在日常的业务开发中,汇总记录使用select count(*) from t
是常见用法,分页查询的时候都会用到,那么随着系统记录的数量越来越多,你有没有发现这条语句执行的也越来越慢了呢?
一、count(*)原理
在不同的存储引擎中,count(*)有不同的实现方式
- MyISAM 存储引擎中,把一个表的总行数存在了磁盘上,因此执行 count(*)的时候,直接读取这个值就行了,效率很高
- 而 InnoDB 执行 count(*)的时候,需要把一行一行数据从存储引擎里读取出来,然后累计计数
当然我们讨论的是没有带 where 语句的,如果带了 where 语句,MyISAM 返回的也不会这么快的
那么问题来了,为什么 InnoDB 不能想 MyISAM 一样,把总数存储起来,而要一行一行的读呢?
原因就是InnoDB是支持事务的,由于多版本并发控制(MVCC)的原因,InnoDB在不同时刻调用count(*)的数据是不确定的
这里举个例子,假设 T 表中有 10000 条数据,设计三个会话:
sessionA | sessionB | sessionB |
---|---|---|
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技术和记录生活的博主。
欢迎扫码关注“一零贰肆”的公众号,一起学习,共同进步,多看路,少踩坑。
标签:count,为什么,返回,一行,索引,InnoDB,越来越,主键 From: https://www.cnblogs.com/sun2020/p/18131148