文章目录
- MySQL45讲 第十四讲 count(*)这么慢,我该怎么办?
- 一、count (*) 实现方式及性能问题
- (一)不同引擎的实现差异
- (二)InnoDB 的优化措施
- 二、计数方案探讨
-
- 三、count 不同用法的性能差别
-
- 四、总结
MySQL45讲 第十四讲 count(*)这么慢,我该怎么办?
一、count (*) 实现方式及性能问题
(一)不同引擎的实现差异
- MyISAM 引擎将表的总行数存储在磁盘上,执行
count (*)
时可直接返回,效率高。但 MyISAM 不支持事务。如果加了where 条件的话,MyISAM表也是不能返回得这么快的。 - 在实际应用中,InnoDB 引擎使用更为广泛。InnoDB引擎执行
count (*)
时,需逐行读取数据并累积计数,这是因为在多版本并发控制(MVCC)机制下,不同事务对表总行数的 “可见性” 不同。例如,在一个同时有事务插入数据的场景中,不同时刻启动的事务查询 count (*)
可能得到不同结果,所以 InnoDB只能逐行判断每行记录是否对当前查询可见,进而确定总行数。
(二)InnoDB 的优化措施
- InnoDB 是索引组织表,普通索引树比主键索引树小。对于
count (*)
操作:
- MySQL优化器会选择遍历最小的索引树以减少扫描数据量。然而,即便如此,随着表中记录数增多,直接使用
count (*)
仍会导致性能问题。 show table status
命令虽执行快,但其中的TABLE_ROWS
值是通过采样估算得来,误差可达 40% - 50%,不能准确替代 count (*)
使用。
二、计数方案探讨
(一)缓存系统保存计数
- 对于更新频繁的库,可使用 Redis 等缓存系统保存表的总行数,表数据插入或删除时相应更新 Redis 计数。但这种方式存在问题:
- 缓存系统可能丢失更新。即使将 Redis 数据持久化存储,仍可能因异常重启等情况丢失计数更新操作。
- 逻辑上不精确。在并发系统中,由于操作顺序难以精确控制,可能出现数据不一致情况,如页面显示的记录与 Redis 计数不匹配。
(二)数据库保存计数
- 将计数直接存于数据库单独的计数表 C 中,利用 InnoDB 支持事务的特性,可解决崩溃丢失问题。在事务执行过程中,通过合理的事务隔离机制,可确保计数与表数据在逻辑上的一致性,避免了缓存系统中因并发操作导致的计数不精确问题。
三、count 不同用法的性能差别
(一)语义解释
count ()
是聚合函数,对于返回结果集逐行判断,参数非 NULL 时累计值加 1。count (*)、count (主键 id) 和 count (1) 都返回满足条件结果集的总行数,count (字段) 返回满足条件数据行中参数 “字段” 不为 NULL 的总个数。
(二)性能对比
- 对于 count (主键 id),InnoDB 引擎遍历整张表取每行 id 值返回给 server 层,server 层判断非空后累加,涉及解析数据行和拷贝字段值操作:
- count (1) 则遍历整张表但不取值,server 层每行放数字 “1” 判断非空后累加,所以 count (1) 比 count (主键 id) 执行快。
- count (字段) 若字段定义为 not null,逐行读取判断非空累加,若字段允许为 null,还需取值进一步判断,性能较差。
count (*)
专门优化不取值,按行累加,效率较高。建议尽量使用 count (*)
。
- 按照效率排序的话,
count(字段)<count(主键id)<count(1)≈count(*)
,所以尽量使用count(*)
。
四、总结
- MySQL 中不同引擎 count (*) 实现方式不同,InnoDB 因 MVCC 机制不能像 MyISAM 直接返回总行数。
- 在处理频繁变更且需统计表行数的需求时,Redis缓存系统保存计数虽读写快但存在丢失更新和逻辑不精确问题。而在数据库中利用事务特性,把这个计数直接放到数据库里单独的一张计数表C中,保存计数可解决一致性问题。
- 同时,了解 count 不同用法性能差别有助于优化查询语句,
count(字段)<count(主键id)<count(1)≈count(*)
,在实际应用中应根据具体需求选择合适的计数方案,充分发挥 InnoDB 引擎事务特性简化业务逻辑,确保数据的准确性和系统性能。