count(?)为什么越来越慢?

在日常的业务开发中,汇总记录使用select count(*) from t是常见用法,分页查询的时候都会用到,那么随着系统记录的数量越来越多,你有没有发现这条语句执行的也越来越慢了呢?

一、count(*)原理

在不同的存储引擎中,count(*)有不同的实现方式

  • MyISAM 存储引擎中,把一个表的总行数存在了磁盘上,因此执行 count(*)的时候,直接读取这个值就行了,效率很高
  • 而 InnoDB 执行 count(*)的时候,需要把一行一行数据从存储引擎里读取出来,然后累计计数

当然我们讨论的是没有带 where 语句的,如果带了 where 语句,MyISAM 返回的也不会这么快的

那么问题来了,为什么 InnoDB 不能想 MyISAM 一样,把总数存储起来,而要一行一行的读呢?

原因就是InnoDB是支持事务的,由于多版本并发控制(MVCC)的原因,InnoDB在不同时刻调用count(*)的数据是不确定的

这里举个例子,假设 T 表中有 10000 条数据,设计三个会话:

sessionAsessionBsessionB
select count(*) from t; (返回 10000)
insert into t (插入一行)
insert into t(插入一行)
select count(*) from t;(返回 10000)select count(*) from t;(返回 10002)select count(*) from t;(返回 10001)

可以看出不同时期的 count()返回的值是不一样的,每一行记录都要判断自己是否对这个会话可见,因此对 count()来说,InnoDB 只有把每一行都读出来做判断,可见性行的才能用于计算总行数

那么 InooDB 是怎么统计记录的呢?

InnoDB 是索引组织表,主键索引树的叶子节点存放的是数据,而普通索引的叶子节点存放的是主键值。所以这就导致了普通索引是比主键索引小很多的,对于 count(*)来说,遍历哪一个索引树在结果逻辑上都是一样的,因此MySQL优化器会找到最小的那一颗索引树来遍历,在保证逻辑结果正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一

二、几种 count(x)的区别

当然肯定有同学见过count(*),count(1),count(主键)count(字段)这几种用法,那么这几种 count 都有什么区别呢?

首先 count()是一个聚合函数,对于返回的结果集,一行一行的判断,如果 count 函数的参数不是 null,累计值就+1,否则不加,最终返回累计值

  • count(1):InnoDB 会扫描遍历整张表,但不取值。server 层对于返回的每一行,判断不为NULL的,进行+1累计
  • count(主键):InnoDB 会扫描遍历整张表,把每一行的 ID 都取出来,返回给 server 层,server 拿到 ID 后判断是否为 NULL,然后按行累计

但从两个用法的差距来看,count(1)会比count(主键)快一点,因为 count(主键)从引擎返回 id 会设计到解析数据行,以及拷贝字段值的操作

  • count(字段):InnoDB 一行行从记录中读取出这个字段,判断是否为NULL,不为NULL才累加
  • count(*): count(*)是例外,并不会把所有字段取出来,而是专门做了优化,不取值,count(*)肯定不为 NULL,按行累加

所以总结来说:count(*) ≈ count(1) > count(主键) > count(字段)


我是一零贰肆,一个关注Java技术和记录生活的博主。

欢迎扫码关注“一零贰肆”的公众号,一起学习,共同进步,多看路,少踩坑。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值