一.count(*) 的实现方式
不同MySQL引擎,count(*)有不同的实现方式
- MyISAM引擎把一个表的总行数存在磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高;
- InnoDB引擎需要一行一行从引擎里面读出来然后累计计数
如果加了where条件,MyISAM表也是不能返回这么快
二.为什么InnoDB不把MyISAM将数据存起来呢?
即使是在同一时刻的多个查询,由于多版本并发控制(MVVC),InnoDB表应该返回多少行也不确定。
- InnoDB是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。
- 普通索引树比主键索引树小很多,对count(*)这样的操作,遍历那个索引树得到的结果都是一样的。
MySQL优化器会找最小的那棵树来遍历。
保证逻辑正确的前提下,尽量减少扫描的数据量,这是数据库系统设计 的通用法则之一
三.show table status
show table status命令显示的行数也不能直接使用。
四.数据库总结
- MyISAM 表虽然 count(*) 很快,但是不支持事务;
- show table status 命令虽然返回很快,但是不准确;
- InnoDB 表直接 count(*) 会遍历全表,虽然结果准确,但会导致性能问题。
五.缓存系统保存计数
一.设计思路:
- 这个表每插入一行Redis计数就加一
- 每被删除一行计数就减一
- 这种情况下,读和更新操作都很快
缓存系统可能会丢失更新
- 刚数据表插入了一行,Redis中保存的值也加了一行,然后Redis异常重启了,重启后从存储的redis数据的地方将值读回来,刚刚加一这个计数就会丢失。
解决方案:
- Redis异常重启后,到数据库里面单独执行一次count(*)获取真实的行数,再将值回写到Redis里就可以了
在并发系统里面,我们是无法精确控制不同线程的执行时刻,无论是先写缓存后写数据库还是先写数据库后写缓存都很不能保证绝对精准。
六.count(*)、count(主键 id)、count(字段) 和 count(1) 等不同用法的性能
- 对于count(主键id)来说,InnoDB引擎回遍历整张表,将每一行id取出来,server层判空。
- 对于 count(1) 来说,。server 层对于返回InnoDB 引擎遍历整张表,但不取值的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
count(1) 执行得要比 count(主键 id) 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作
对于 count(字段) 来说:
- 如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;
- 如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。
也就是前面的第一条原则,server 层要什么字段,InnoDB 就返回什么字段。
count(*)专门优化了
count(字段)<count(主键id)<count(1)==count(*)
推荐count(*)!