我说用count(*)统计行数,面试官让我回去等消息...

  • 前言

  • 1 count(*)为什么性能差?

  • 2 如何优化count(*)性能?

    • 2.1 增加redis缓存

    • 2.2 加二级缓存

    • 2.3 多线程执行

    • 2.4 减少join的表

    • 2.5 改成ClickHouse

  • 3 count的各种用法性能对比


前言

最近我在公司优化过几个慢查询接口的性能,总结了一些心得体会拿出来跟大家一起分享一下,希望对你会有所帮助。

我们使用的数据库是Mysql8,使用的存储引擎是Innodb。这次优化除了优化索引之外,更多的是在优化count(*)

通常情况下,分页接口一般会查询两次数据库,第一次是获取具体数据,第二次是获取总的记录行数,然后把结果整合之后,再返回。

查询具体数据的sql,比如是这样的:`

select id,name from user limit 1,20;

它没有性能问题。

但另外一条使用count(*)查询总记录行数的sql,例如:

select count(*) from user;

却存在性能差的问题。

为什么会出现这种情况呢?

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

1 count(*)为什么性能差?

在Mysql中,count(*)的作用是统计表中记录的总行数。

count(*)的性能跟存储引擎有直接关系,并非所有的存储引擎,count(*)的性能都很差。

在Mysql中使用最多的存储引擎是:innodbmyisam

在myisam中会把总行数保存到磁盘上,使用count(*)时,只需要返回那个数据即可,无需额外的计算,所以执行效率很高。

而innodb则不同,由于它支持事务,有MVCC(即多版本并发控制)的存在,在同一个时间点的不同事务中,同一条查询sql,返回的记录行数可能是不确定的。

在innodb使用count(*)时,需要从存储引擎中一行行的读出数据,然后累加起来,所以执行效率很低。

如果表中数据量小还好,一旦表中数据量很大,innodb存储引擎使用count(*)统计数据时,性能就会很差。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

2 如何优化count(*)性能?

从上面得知,既然count(*)存在性能问题,那么我们该如何优化呢?

我们可以从以下几个方面着手。

2.1 增加redis缓存

对于简单的count(*),比如:统计浏览总次数或者浏览总人数,我们可以直接将接口使用redis缓存起来,没必要实时统计。

当用户打开指定页面时,在缓存中每次都设置成count = count+1即可。

用户第一次访问页面时,redis中的count值设置成1。用户以后每访问一次页面,都让count加1,最后重新设置到redis中。

这样在需要展示数量的地方,从redis中查出count值返回即可。

该场景无需从数据埋点表中使用count(*)实时统计数据,性能将会得到极大的提升。

不过在高并发的情况下,可能会存在缓存和数据库的数据不一致的问题。

但对于统计浏览总次数或者浏览总人数这种业务场景,对数据的准确性要求并不高,容忍数据不一致的情况存在。

2.2 加二级缓存

对于有些业务场景,新增数据很少,大部分是统计数量操作,而且查询条件很多。这时候使用传统的count(*)实时统计数据,性能肯定不会好。

假如在页面中可以通过id、name、状态、时间、来源等,一个或多个条件,统计品牌数量。

这种情况下用户的组合条件比较多,增加联合索引也没用,用户可以选择其中一个或者多个查询条件,有时候联合索引也会失效,只能尽量满足用户使用频率最高的条件增加索引。

也就是有些组合条件可以走索引,有些组合条件没法走索引,这些没法走索引的场景,该如何优化呢?

答:使用二级缓存

二级缓存其实就是内存缓存。

我们可以使用caffine或者guava实现二级缓存的功能。

目前SpringBoot已经集成了caffine,使用起来非常方便。

只需在需要增加二级缓存的查询方法中,使用@Cacheable注解即可。

 @Cacheable(value = "brand", , keyGenerator = "cacheKeyGenerator")
   public BrandModel getBrand(Condition condition) {
       return getBrandByCondition(condition);
   }

然后自定义cacheKeyGenerator,用于指定缓存的key。

public class CacheKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return target.getClass().getSimpleName() + UNDERLINE
                + method.getName() + ","
                + StringUtils.arrayToDelimitedString(params, ",");
    }
}

这个key是由各个条件组合而成。

这样通过某个条件组合查询出品牌的数据之后,会把结果缓存到内存中,设置过期时间为5分钟。

后面用户在5分钟内,使用相同的条件,重新查询数据时,可以直接从二级缓存中查出数据,直接返回了。

这样能够极大的提示count(*)的查询效率。

但是如果使用二级缓存,可能存在不同的服务器上,数据不一样的情况。我们需要根据实际业务场景来选择,没法适用于所有业务场景。

2.3 多线程执行

不知道你有没有做过这样的需求:统计有效订单有多少,无效订单有多少。

这种情况一般需要写两条sql,统计有效订单的sql如下:

select count(*) from order where status=1;

统计无效订单的sql如下:

select count(*) from order where status=0;

但如果在一个接口中,同步执行这两条sql效率会非常低。

这时候,可以改成成一条sql:

select count(*),status from order
group by status;

使用group by关键字分组统计相同status的数量,只会产生两条记录,一条记录是有效订单数量,另外一条记录是无效订单数量。

但有个问题:status字段只有1和0两个值,重复度很高,区分度非常低,不能走索引,会全表扫描,效率也不高。

还有其他的解决方案不?

答:使用多线程处理。

我们可以使用CompleteFuture使用两个线程异步调用统计有效订单的sql和统计无效订单的sql,最后汇总数据,这样能够提升查询接口的性能。

2.4 减少join的表

大部分的情况下,使用count(*)是为了实时统计总数量的。

但如果表本身的数据量不多,但join的表太多,也可能会影响count(*)的效率。

比如在查询商品信息时,需要根据商品名称、单位、品牌、分类等信息查询数据。

这时候写一条sql可以查出想要的数据,比如下面这样的:

select count(*)
from product p
inner join unit u on p.unit_id = u.id
inner join brand b on p.brand_id = b.id
inner join category c on p.category_id = c.id
where p.name='测试商品' and u.id=123 and b.id=124 and c.id=125;

使用product表去join了unit、brand和category这三张表。

其实这些查询条件,在product表中都能查询出数据,没必要join额外的表。

我们可以把sql改成这样:

select count(*)
from product
where name='测试商品' and unit_id=123 and brand_id=124 and category_id=125;

在count(*)时只查product单表即可,去掉多余的表join,让查询效率可以提升不少。

2.5 改成ClickHouse

有些时候,join的表实在太多,没法去掉多余的join,该怎么办呢?

比如上面的例子中,查询商品信息时,需要根据商品名称、单位名称、品牌名称、分类名称等信息查询数据。

这时候根据product单表是没法查询出数据的,必须要去join:unit、brand和category这三张表,这时候该如何优化呢?

答:可以将数据保存到ClickHouse

ClickHouse是基于列存储的数据库,不支持事务,查询性能非常高,号称查询十几亿的数据,能够秒级返回。

为了避免对业务代码的嵌入性,可以使用Canal监听Mysqlbinlog日志。当product表有数据新增时,需要同时查询出单位、品牌和分类的数据,生成一个新的结果集,保存到ClickHouse当中。

查询数据时,从ClickHouse当中查询,这样使用count(*)的查询效率能够提升N倍。

需要特别提醒一下:使用ClickHouse时,新增数据不要太频繁,尽量批量插入数据。

其实如果查询条件非常多,使用ClickHouse也不是特别合适,这时候可以改成ElasticSearch,不过它跟Mysql一样,存在深分页问题。

3 count的各种用法性能对比

既然说到count(*),就不能不说一下count家族的其他成员,比如:count(1)、count(id)、count(普通索引列)、count(未加索引列)。

那么它们有什么区别呢?

  • count(*) :它会获取所有行的数据,不做任何处理,行数加1。

  • count(1):它会获取所有行的数据,每行固定值1,也是行数加1。

  • count(id):id代表主键,它需要从所有行的数据中解析出id字段,其中id肯定都不为NULL,行数加1。

  • count(普通索引列):它需要从所有行的数据中解析出普通索引列,然后判断是否为NULL,如果不是NULL,则行数+1。

  • count(未加索引列):它会全表扫描获取所有数据,解析中未加索引列,然后判断是否为NULL,如果不是NULL,则行数+1。

由此,最后count的性能从高到低是:

count(*) ≈ count(1) > count(id) > count(普通索引列) > count(未加索引列)

所以,其实count(*)是最快的。

意不意外,惊不惊喜?

千万别跟select * 搞混了。

我的尤克里里
关注 关注
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
有了这篇,面试官问MySQL,再也不虚了!(二)
03-31 196
写在前面: 最近学习了大佬的MySQL课程,很有深度,解开了我很多疑点,得去研究学习,这里整理记录下。 当然这里面需要一些前置知识储备,详见: 二叉树、红黑树、B树、B+树 合集 MySQL相关知识点 一、随机查询 现象:英语学习 App 首页有一个随机显示单词的功能,也就是根据每个用户的级别有一个单词表,然后这个用户每次访问首页的时候,都会随机滚动显示三个单词。发现随着单词表变大,选单词这个逻辑变得越来越慢,甚至影响到了首页的打开速度。 mysql> CREATE TABLE.
Mybatis Plus中的selectCount的使用
m0_54861649的博客
03-08 9948
常用的方法一般是在mapper.xml中写一个 select count(user\_code) from sys\_law\_case\_project\_user where user\_code =#{userCode} 写一个select块来调用查询。 在mybatis plus中有集成好的selectCount的方法。 Integer selectCount(@Param(“ew”) Wrapper queryWrapper); 使用方式如下: @Override pub
Mybatis puls 查询返回null或者count为0,但通过打印的sql查询有数据
祁东握力的博客
09-20 1756
3、打了断点,拿sql到navicat中去查能查到,是因为在另一个事务中读取到是当前事务开始之前的数据(未删除或未修改)。Mybatis puls 查询返回null或者count为0,停在debug断点,通过打印的sql查询却有数据。排查自己代码,当前事务中,在执行查询之前是否有删除或修改操作。如果不是我这个原因导致的,那可能是resultMap或者字段映射对不上之类的比较低级的错误导致的。2、在本事务中,执行查询之前,有对这部分数据进行删除或者修改操作,导致查询不到。
mybatis plus分页查询count数量和返回list结果不一致
tqy的博客
04-11 4539
本地调试一个方法,方法名为 模拟分页参数为 ,出现奇怪的现象 分页插件使用threadlocal保存当前的分页参数并在调用查询的时候先count一次,此问题现象为count的时候是有1条数据的,但是返回的list结果集却没有数据并且没有打印执行select list的sql语句,好奇怪啊,按道理是两者的数据量肯定是一样的并且要执行2次sql查询 于是进入debug查看,首先进入到executeForMany方法 接着进入selectList方法 继续进入P..
mysql-mybatis-“select count (1)” select sum(1)是什么意思?及IFNULL() 函数
鹏神丶明月天
02-03 1641
一般情况下,Select Count (*)和Select Count(1)两着返回结果是一样的,假如表没有主键(Primary key), 那么count(1)比count(*)快,如果有主键的话,那主键作为count的条件时候count(主键)最快,如果你的表只有一个字段的话那count(*)就是最快的。同理,count(2),也可以,得到的完全一样,count('x'),count('y')都是可以的。select count(*)返回所有满足条件的记录数,此时同select sum(1)
mybatis-plus 分页查询出现count()而不是count(*)
god_sword_的博客
07-02 1837
使用mybatis-plus 3.4.2 分页 SELECT COUNT() FROM
count(*)统计行数面试官让我回去消息
jjc4261的博客
11-27 208
最近我在公司优化过几个慢查询接口的性能,总结了一些心得体会拿出来跟大家一起分享一下,希望对你会有所帮助。我们使用的数据库是Mysql8,使用的存储引擎是Innodb。这次优化除了优化索引之外,更多的是在优化count(*)。通常情况下,分页接口一般会查询两次数据库,第一次是获取具体数据,第二次是获取总的记录行数,然后把结果整合之后,再返回。查询具体数据的sql,比如是这样的:select id,name from user limit&
SELECT COUNT(*) 查询很慢,面试官让我回去等通知
weixin_49527334的博客
10-28 2710
前言 之前提到使用以下 sql 会导致慢查询 SELECT COUNT(*) FROM SomeTable SELECT COUNT(1) FROM SomeTable 原因是会造成全表扫描,有位读者是有问题的,实际上针对无 where_clause 的 COUNT(*),MySQL 是有优化的,优化器会选择成本最小的辅助索引查询计数,其实反而性能最高,这位读者的法对不对呢 针对这个疑问,我首先去生产上找了一个千万级别的表使用 EXPLAIN 来查询了一下执行计划 EXPLAIN SELECT CO
2万字软件测试面试题干货带答案,反手我就一个收藏
m0_57290404的博客
06-14 1722
需求分析、编写测试用例、评审测试用例、搭建环境、等待程序开发包、部署程序开发包、冒烟测试、执行具体的测试用例细节、Bug 跟踪处理回归测试、N 轮之后满足需求,测试结束第一类标准:测试超过了预定时间,则停止测试。第二类标准:执行了所有的测试用例,但并没有发现故障,则停止测试。第三类标准:使用特定的测试用例设计方案作为判断测试停止的基础第四类标准:正面指出停止测试的具体要求,即停止测试的标准可定义为查出某一预订数目的故障。第五类标准:根据单位时间内查出故障的数量决定是否停止测试1) 应当把“尽早地和不断地进行
PHP 高级工程面试题汇总
weixin_43814458的博客
11-12 1266
1、给你四个坐标点,判断它们能不能组成一个矩形,如判断 ([0,0],[0,1],[1,1],[1,0]) 能组成一个矩形。 勾股定理,矩形是对角线相等的四边形。只要任意三点不在一条直线上,任选一点,求这一点到另外三点的长度的平方,两个短的之和如果等于最长的,那么这就是矩形。 2、写一段代码判断单向链表中有没有形成环,如果形成环,请找出环的入口处,即 P 点 /* *单链表的结点类 *...
MySQL 基础模块的面试题总结
凯凯的博客
09-26 1055
一下 MySQL 执行一条查询语句的内部执行过程? 客户端先通过连接器连接到 MySQL 服务器。 连接器权限验证通过之后,先查询是否有查询缓存,如果有缓存(之前执行过此语句)则直接返回缓存数据,如果没有缓存则进入分析器。 分析器会对查询语句进行语法分析和词法分析,判断 SQL 语法是否正确,如果查询语法错误会直接返回给客户端错误信息,如果语法正确则进入优化器。 优化器是对查询语句进行优化处理...
mybatis的各种查询情况3 - 查询信息总量 count(*)以及返回总量
热门推荐
m0_53753920的博客
04-16 1万+
前面介绍了mybatis的各种查询情况的两种,接下去我们来看看其他的查询吧~~~~~ 本节中返回对象有新知识点,让我们一起提高吧。gogogo!!! 目录 1.接口部分 2.mapper映射文件部分 2.1大纲碎碎念 2.2 resultType返回类型 3.测试类 4.结果 4.1测试类结果 4.2数据库验证 5.结论 1.接口部分 接口部分用Integer或者int类型都是可以的,返回一个整数。 /** * 查询用户信息总记录数 */
[SpringBoot]MyBatis Plus框架&使用selectCount
YJH000_的博客
06-02 6938
MyBatis Plus是一个基于MyBatis的增强工具,它简化了MyBatis的开发流程,提供了更便捷、高效的数据库访问解决方案。MyBatis Plus在保留了MyBatis核心特性的基础上,提供了许多额外的功能和扩展,使得开发者可以更快速地编写数据库操作代码。以下是MyBatis Plus的一些主要特性:简化的CRUD操作:MyBatis Plus提供了丰富的CRUD操作方法,可以通过简单的接口方法完成常见的增删改查操作,无需编写SQL语句。
后台返回的数字,有可能是字符串
boysky0015的博客
08-15 1587
let num = '1'; console.log(num); //1 这个num实际是字符串,但在控制台打印出来是1,我们误认为是number,但其实是string类型。一般没什么,但有时候却致命。因此我们要注意这个问题。 一般我们可以用typeof num 判断是string还是number类型。 console.log(typeof num); //string' 如何把字符数...
MybatisPlus的LambdaQueryWrapper用法
2301_79693537的博客
08-21 5426
【代码】MybatisPlus的LambdaQueryWrapper用法。
【C++刷题】力扣-#121-买卖股票的最佳时机
会写代码的饭桶
10-16 610
给定一个数组 prices,其中 prices[i] 表示第 i 天的股票价格。假设你可以在第 i 天买入并在第 j 天卖出股票(i ≤ j),设计一个算法来计算你所能获取的最大利润。注意你只能持有一股股票,并且你不能同时参与多笔交易(即在再次买入前必须卖出股票)。
java 异常包装
最新发布
fdvvg的博客
10-17 376
通过异常包装,可以在Java中有效地处理和传递异常。这样做不仅能保持原始异常的信息,还能为上层调用者提供更多的上下文信息。
基于SSM班级事务管理系统的设计
2401_87849773的博客
10-15 470
管理员账户功能包括:系统首页,个人中心,学生管理,班委管理,班会组织管理,健康档案管理,党员发展管理,党员培训管理,学生成绩管理。主要技术:Java,Spring,mybatis,mysql,jquery,html。班委账号功能包括:系统首页,学生管理,学生成绩管理,活动信息管理,班费通知管理。服务器:SpringBoot自带 apache tomcat。JDK版本:Java JDK1.8。数据库可视化工具: navicat。数据库版本: mysql5.7。开发系统:Windows。
xlrd获取worksheet2.UsedRange.Rows.Count最大行数
10-14
要使用`xlrd`库获取`worksheet2`的`UsedRange`(即实际使用的区域)的最大行数,你可以按照以下步骤操作: 1. 导入必要的库: ```python import xlrd ``` 2. 打开Excel文件并加载工作表: ```python workbook = ...
写文章

热门文章

  • 简化 Hello World:Java 新写法要来了 7026
  • UUID的弊端以及雪花算法 5239
  • SQL开源替代品,诞生了 3510
  • SpringBoot 最新版3.x 集成 OAuth 2.0 实现认证授权服务、第三方应用客户端以及资源服务 3182
  • SpringBoot整合Canal+RabbitMQ监听数据变更 3184

最新评论

  • RabbitMQ使用延迟插件,代码量直接减少一半!

    java丶小萌新: 是不是少了QueueEnum的代码。。

  • SpringBoot 最新版3.x 集成 OAuth 2.0 实现认证授权服务、第三方应用客户端以及资源服务

    久伴爱学习: 楼主有没有开源的代码

  • 少走点弯路,这些 Java 技术已经被淘汰了,别再学了

    不要和代码过不去: 不错 听君一席话 少读两本书

  • 简化 Hello World:Java 新写法要来了

    躲在云后的雨: 还有好多要学

最新文章

  • ES+Redis+MySQL,这个高可用架构设计太顶了
  • 2023需求最高的编程语言:Python、JavaScript和Java
  • SQLSERVER 居然也能调 C# 代码 ?
2023年130篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的尤克里里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家温州商场商业美陈报价栖霞秋季商场美陈肇庆商场玻璃钢卡通雕塑广西玻璃钢卡通雕塑海豚厂家关公玻璃钢雕塑施工哪家好优质玻璃钢雕塑多少钱长春小区玻璃钢雕塑制作成都玻璃钢雕塑安装福建商场主题创意商业美陈理念平湖玻璃钢雕塑生产厂北京大兴玻璃钢景观雕塑陕西玻璃钢校园玻璃钢景观雕塑湖北城市雕塑玻璃钢德兴玻璃钢牛雕塑玻璃钢人物校园不锈钢雕塑定做价格划算的玻璃钢花盆艺术商场美陈批发厦门小区玻璃钢雕塑安装温县玻璃钢雕塑厂家商场美陈考核表西安基督教玻璃钢雕塑阳江玻璃钢果蔬雕塑上海特色玻璃钢雕塑定做价格韶关玻璃钢雕塑铜玻璃钢雕塑图片大全听海雕塑青岛玻璃钢卡通宿迁玻璃钢海豚雕塑定制贵州水果玻璃钢雕塑设计菏泽佛像玻璃钢雕塑巢湖玻璃钢卡通雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化