聊一聊关于MySQL的count(*)

  1. 背景
    自从大家对于MySQL数据库的稳定性有了更高的追求后,经常有小伙伴有这样的疑问,对于count(*)这样的操作,有没有正确的姿势,或者有没有可以优化7 j l的地方?
    聊一聊关于MySQL的count(*)

但答案比较残酷,如果已经使用了正确的索引,那么基本上没有可以优化的地方。E n 1 E P h C一旦出现慢查询Q e 1 f w,它就是慢查询了,要改,只能自己R x W U [ ~ 9计数或者通过其他搜索平台来做。
今天,就一起来看看为什么O ^ 7 f会这样,并对大家日常会遇到的一些的困惑进行解答。

  1. count()的实现方式
    据说,MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(
    ) 的时候会直接返回这个数,效率很高。
    而我们的mysql一般都是用Innodb的引擎,Innodb是怎么实现count操作的呢?
    InnoDB 引擎就比较麻烦了,它执行 count() 的时候) j D c %,需要把数据一行一行地从引擎里面读出来,然后累积计数。
    所以,当k ~ ) ( D - 8我们的表里面的记录越来越多的时候,cou= { p % u | } / nt(
    )就会越来越慢。
    当然,我们这里说的都是不带where条件的,如果带上where条件的话,MyISAM也是很慢的。

3.正确的打开方式
嗯,首先还是说,} 0 4 a k V #mysql上不太推荐用cc Y - 6ount()来做统计相关业务,尤其是c / U表非常大的情况下。
那如果业务比较y 5 ~ k小,需要快速上马,那么,至少应该保证count(
)带上了科学的where条件,然后,这个表也L b R H * K 0 ;已经建立了科学的索引。
1)如果count()带上的where条件,而且能够走覆盖索引,那还是可以偶尔走一走的。
2)如果count(
)带上的where条件,能够走索引,但是需要回表,那么* R E $ u : U这种就会比较危险,尤其是随着表规模的扩大,终究是一颗雷。
3)如果纯粹count(*),或者where条件没有任何索引,万万万不推荐!
那对于统计类M o T c 5 { W T的业务,推荐的几种做法:& u ~ 8 j
1)带自增id的,可以用最大id来近似获取
2)自己计数
3)其他数据. Y # e `分析平台进行聚合

  1. 能否用表统| [ R 3 j . D计信息代替count()
    有同学在日常使用过程中,问能否使用 系统表的统计信息 来代替count。
    答案是不行。这里的tableRows只是一个参考值。
    这里z L E } j的表统计信息,实际上是使用show t9 X ( v $ _able status获取的。这个值是如何得到的呢?我们需要了解下mysql的采样统计方法。
    为什么要采样统计呢?因为把整张表取出来一行行统计,虽然可以得到z I N a c w W 1 N精确的结果,但是代价太高了,所以只能选择“采样统计”(所以其实mysql自己0 | ? W也没有count(
    )的好方法)。
    采样统计的时候,InnoDB 默认A B N w p P选择 N 个数据页,统计这些页面上的) 2 6 e , h = |不同值,得到一个平H z d | c F ^ 均值,然后乘以这个索引的页面数,就得到了这个索引的基数。
    而数据表是会持续更新的,索9 O G引统计信息也不会固定不变。所以,当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。
    因此,这个采样估算得来的值,是很不准的。有多不准呢,官方文档说误差可能达` Z 3 c / 9 8 h J到 40% 到 50%。

4.关于那些奇奇怪怪的count(?)
在看一些老代码查询的时候,我们经常会看到count(1),co; % i { ( 6 yunt(id),count(字段)等方式,那它们纠结孰优孰劣,到底有没有性能上的差异呢?
这里,我们先要弄清楚 c ) ? 8ount() 的语义。
count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最[ ! L后返回累计值。
1)count(主键id)
InnoDB 引擎t | f 6会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。
2)count(1)
InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
3) count(字段)
如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;
如果这个“字段”定义允许为 nh U B ; ; Z k rull,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。
4)count()
并不会把全j r z J b & d部字段取出来,而是专门做了优化,不取值。count(
) 肯定不是 null,按行累加。
所以结论是:按照效率排序的话,count(字段)<count(主键 id)<count(1)≈count(),所以我建议,尽量使用 count()。