一、抛砖引玉
MySQL的count(*) 语句到底是怎样实现的,以及 MySQL 为什么会这么实现?
二、正文开始
🏁:1.这个问题回答之前需要从不同的数据库引擎分析
- MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高;
- InnoDB 引擎它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
⚠️⚠️⚠️:你要是加了where条件MyISAM也懵逼,不会返回这么快
👋:2.为什么Innodb不把数字事先存起来呢?
因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。
举例:
show table status 命令的话,就会发现这个命令的输出结果里面也有一个 TABLE_ROWS 用于显示这个表当前有多少行,这个命令执行挺快的,那这个 TABLE_ROWS 能代替 count(*) 吗?
不能!!! 因为索引统计的值是通过采样来估算的,不准的概率达到40-50%
🏁:3.这时候如果用缓存计数会有什么问题?不用缓存还有什么方案可以替代。
用缓存系统保存计数有丢失数据和计数不精确的问题。那么,如果我们把这个计数直接放到数据库里单独的一张计数表 C 中,又会怎么样呢?
🏁:4.面试题来了!!!
count(*)、count(主键 id)、count(字段) 和 count(1) 等不同用法的性能,有哪些差别?
⭐️:非性能差别。
4.1.count(*)、count(主键 id) 和 count(1) 都表示返回满足条件的结果集的总行数;而 count(字段),则表示返回满足条件的数据行里面,参数“字段”不为 NULL 的总个数。
-
count(id)。InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。
-
count(1) 。InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
🚩:count(1) 要比count(id)要快,因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。
-
对于 count(字段) 来说分为两种情况:这个字段为not null还是被定义为了null,定义为null时,还需要把值取出来在判断一下不是null才累加。
⚠️⚠️⚠️:这里没有分析字段上有没有索引,**如果字段上没有加索引。count(字段)会比count(id)慢!**因为,count(id)可能会选择最小的索引来遍历,而count(字段)的话,如果字段上没有索引,就只能选主键索引 -
但是 count() 是例外,并不会把全部字段取出来,而是专门做了优化,不取值。count() 肯定不是 null,按行累加。
所以结论是:
count(字段):遍历整张表,需要取值,判断 字段 != null,按行累加;
count(id) :遍历整张表,需要取ID,判断 id !=null,按行累加;
count(1) : 遍历整张表,【不需要】取值,返回的每一行放一个数字1,按行累加;
count() : 【不需要取字段】,count(),按行累加; 因为count(*) 和 count(1) 不取字段值,减少往 server层的数据返回,所以比其他count(字段)要返回值的【性能】较好;
三、思考题
由于事务可以保证中间结果不被别的事务读到,因此修改计数值和插入新记录的顺序是不影响逻辑结果的。但是,从并发系统性能的角度考虑,你觉得在这个事务序列里,应该先插入操作记录,还是应该先更新计数表呢?
先插入新纪录,因为插入新记录只会影响到行锁和间隙锁,而先更新计数表会占用计数表的写锁,而很多其他事务的插入操作就必须阻塞等待
四、评论中的精华
🏁:5.MySQL 什么是幻读?如何解决?
文章中的案例中并没有幻读现象
请看下一篇文章分析
[https://blog.csdn.net/hxy_lbj/article/details/115125950](14-01 | MySQL 幻读如何解决?)
👋:对于 count(主键 id) ,server层拿到ID,判断ID是不可能为空的按行累加。这个地方,是不是又点问题,既然是主键ID,是一定不会为空的,这个server层还需要判断不为空吗
答:代码就是这么写的 我也觉得可以优化一下… 不过现在就这样