count(*)、count(1)、count(字段)有什么区别?


前言

大家好,我是练习两年半的Java练习生,最近阅读了《mysql45实战讲》,复习了Mysql的各部分内容,所以想出一个专栏和大家分享一下!


count(*)的实现方式

你首先要明确的是,在不同的MySQL引擎中,count(*)有不同的实现方式。

  • MyISAM引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高;
  • 而InnoDB引擎就麻烦了,它执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

对于count(*)这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。

用缓存系统保存计数

使用redis计数

可以用一个Redis服务来保存这个表的总行数。这个表每被插入一行Redis计数就加1,每被删除一行Redis计数就减1。

将计数保存在缓存系统中的方式,还不只是丢失更新的问题。即使Redis正常工作,这个值还是逻辑上不精确的。

我们是这么定义不精确的:

  1. 一种是,查到的100行结果里面有最新插入记录,而Redis的计数里还没加1;
  2. 另一种是,查到的100行结果里没有最新插入的记录,而Redis的计数里已经加了1。

这两种情况,都是逻辑不一致的。

如下图:
image.png

如果反过来,先加redis,在更新数据库,也不行
image.png

本质上,就是更新操作和数目统计不能在同一个事务里面。

使用数据库保存计数

我们能够通过事务,解决逻辑不正确问题。
image.png

我们来看下现在的执行结果。虽然会话 B 的读操作仍然是在 T3 执行的,但是因为这时候更新事务还没有提交,所以计数值加 1 这个操作对会话 B 还不可见。

因此,会话 B 看到的结果里, 查计数值和“最近 100 条记录”看到的结果,逻辑上就是一致的。

不同count 用法

count(*)、count(主键 id) 和 count(1) 都表示返回满足条件的结果集的总行数

而 count(字段),则表示返回满足条件的数据行里面,参数“字段”不为 NULL 的总个数。

至于分析性能差别的时候,可以记住这么几个原则:

  1. server 层要什么就给什么;
  2. InnoDB 只给必要的值;
  3. 现在的优化器只优化了 count(*) 的语义为“取行数”,其他“显而易见”的优化并没有做。

count(id)

对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。

如果表里只有主键索引,没有二级索引时,那么,InnoDB 循环遍历聚簇索引,将读取到的记录返回给 server 层,然后读取记录中的 id 值,就会 id 值判断是否为 NULL,如果不为 NULL,就将 count 变量加 1。
image.png
但是,如果表里有二级索引时,InnoDB 循环遍历的对象就不是聚簇索引,而是二级索引。
image.png

这是因为相同数量的二级索引记录可以比聚簇索引记录占用更少的存储空间,所以二级索引树比聚簇索引树小,这样遍历二级索引的 I/O 成本比遍历聚簇索引的 I/O 成本小,因此「优化器」优先选择的是二级索引。

count(1)

对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
如果表里只有主键索引,没有二级索引时。
image.png

那么,InnoDB 循环遍历聚簇索引(主键索引),将读取到的记录返回给 server 层,但是不会读取记录中的任何字段的值,因为 count 函数的参数是 1,不是字段,所以不需要读取记录中的字段值。

参数 1 很明显并不是 NULL,因此 server 层每从 InnoDB 读取到一条记录,就将 count 变量加 1。

可以看到,count(1) 相比 count(主键字段) 少一个步骤,就是不需要读取记录中的字段值,所以通常会说 count(1) 执行效率会比 count(主键字段) 高一点。

count(*)

看到 * 这个字符的时候,是不是大家觉得是读取记录中的所有字段值?
对于 selete * 这条语句来说是这个意思,但是在 count(*) 中并不是这个意思。

count(*) 其实等于 count(0),也就是说,当你使用 count(*) 时,MySQL 会将 * 参数转化为参数 0 来处理。

所以,count(*) 执行过程跟 count(1) 执行过程基本一样的,性能没有什么差异。

而且 MySQL 会对 count(*) 和 count(1) 有个优化,如果有多个二级索引的时候,优化器会使用key_len 最小的二级索引进行扫描。
只有当没有二级索引的时候,才会采用主键索引来进行统计。

count(字段)

count(字段) 的执行效率相比前面的 count(1)、 count(*)、 count(主键字段) 执行效率是最差的。
对于这个查询来说,会采用全表扫描的方式来计数,所以它的执行效率是比较差的。
image.png

同样的,可以给字段加上索引,提高效率。
如果你非要统计表中该字段不为 NULL 的记录个数,建议给这个字段建立一个二级索引。


总结

今天,我们学习了count(1)/count()/count(字段)/count(id)的效率问题,其中效率从高到低应该为
count(
) = count(1) > count(id) > count(字段) 。

好了,这就是我们今天介绍的全部内容了。如果你有什么疑问或者问题,欢迎在评论区指出!


参考连接

https://xiaolincoding.com/ 小林
https://funnylog.gitee.io/mysql45/ mysq实战45讲

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值