为什么我建议你使用 count(*)
MySQL 对 count(*) 的不同引擎实现
MyISAM 引擎把一个表里的总行数存在磁盘上, count(*) 直接返回这个数,会很快。
InnoDB 引擎执行 count(*) 会遍历索引树累积计数,当数据量大时,效率不高。
为什么 InnoDB 不像 MyISAM 将表总行数存起来?
因为 InnoDB 支持事务,根据MVCC原理,每个事务可见的行数是不确定的,因此需要逐条统计可见行。
另外show table status
命令虽然会统计总行数,但因为是采样统计,误差较大,在要求总行数精确的场景下不适用。
计数系统:缓存还是数据库
根据上述背景,我们要实现获取一张表的总行数,只能自己计数。
缓存存在的问题
- 丢失更新:虽然Redis读取内存很快,但是一旦异常重启,会造成数据丢失;
- 数据不一致:由于Redis不支持事务,无法保证多线程的执行序列。
数据库实现
如果用MySQL数据库的一张计数表存放行数是可以解决上述两个问题的。
- 首先InnoDB支持崩溃恢复,不回丢失数据
- 其次InnoDB支持事务MVCC,未提交事务的更新对其它事务不可见,不会看到脏数据。
count() 函数实现原理和性能对比
count()
是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值。
所以,count(*)
、count(主键 id)
和 count(1)
都表示返回满足条件的结果集的总行数;而 count(字段)
,则表示返回满足条件的数据行里面,参数“字段”不为 NULL 的总个数。
count(主键 id)
InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。
server 层拿到 id 后,判断是不可能为空的,就按行累加。
count(1)
InnoDB 引擎遍历整张表,但不取值。
server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
count(字段)
一行行地从记录里面读出这个字段,判断是否为 null,非空按行累加
count(*)
count(*) 是一个例外,因为 MySQL 专门对这个语句进行过优化。
它不会把所有字段取出来,实际上它不取值,只是按行累加。
效率对比
按效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(*),所以可以使用 count(*)。