myisam的count(*)快是因为它将总行数作为磁盘上的固定值存储,无where时直接读取该值;而innodb需按事务一致性视图逐行判断可见性并累加,故必须遍历索引。

MyISAM 的 COUNT(*) 为什么快得像查变量
因为 MyISAM 真的就把总行数存成一个磁盘上的固定值,每次执行 COUNT(*) 就是读一次这个字段,不扫数据、不判断可见性、不走索引树。
但注意:一旦加了 WHERE 条件,它也得老老实实扫描数据——这个“快”只对无条件全表计数有效。
- 建表时没显式指定引擎,默认可能是 InnoDB(尤其 MySQL 5.7+ 和 8.0),别默认以为自己用的是 MyISAM
- MyISAM 不支持事务、行锁和崩溃恢复,线上业务基本不用,仅适合只读报表类场景
-
SHOW TABLE STATUS返回的Rows字段在 MyISAM 下是精确值,在 InnoDB 下只是采样估算(误差常达 40%–50%)
InnoDB 的 COUNT(*) 为什么必须一行行数
不是它不想快,是它不能快。InnoDB 要保证 MVCC 下每个事务看到的行数一致——同一时刻,不同事务对“这张表有多少行”的答案可能完全不同。
所以它必须按当前事务的一致性视图,把每一行捞出来判断是否可见,再累加。哪怕你只想要总数,它也得遍历最小索引(通常是主键或二级索引的叶子节点)。
- 没有
WHERE时,优化器会选择聚集索引或最小二级索引扫描,但仍是 I/O 密集型操作 - 表越大、buffer pool 越紧张(比如 MySQL 8.0.18 的已知 bug),物理读越多,
COUNT(*)越慢甚至超时 -
COUNT(1)、COUNT(pk)、COUNT(*)在 InnoDB 下性能几乎没差别,别迷信“用 1 比 * 快”
别信 SHOW TABLE STATUS 的 Rows 值
这个值对 InnoDB 来说只是个统计采样,InnoDB 会定期采样索引页估算行数,官方明确说明误差范围可达 ±40%~50%。
它适合快速看个量级(比如“千万级”还是“百万级”),但绝不能用于分页总数、后台监控阈值判断或一致性校验。
- 执行
ANALYZE TABLE会触发重新采样,但不保证更准,且本身有开销 - 某些云厂商控制台显示的“总行数”,底层就是调的这个字段,别当真
- 如果应用真需要近似总数且能接受滞后,可考虑用 Redis 异步维护,但要注意重启丢失、主从延迟、事务回滚未扣减等边界问题
真正能落地的替代方案有哪些
硬要 COUNT(*) 得到精确值,又不能换引擎,那就得接受它慢;想快,就得放弃“实时精确”这个前提。
- 业务允许误差:用
SELECT SUM(table_rows) FROM information_schema.tables(仍是估算,且跨库不准) - 写少读多场景:用单独计数表,每次
INSERT/DELETE同事务里UPDATE counter_table SET cnt = cnt + 1 - 大数据量分页:改用游标分页(
WHERE id > ? ORDER BY id LIMIT N),彻底避开COUNT(*) - 监控告警类需求:用采样查询(如
TABLESAMPLE SYSTEM (1),MySQL 8.0.22+)估算趋势,不依赖绝对值
最常被忽略的一点:很多“慢 count”其实源于没意识到自己正在 InnoDB 上执行——查一下 SHOW CREATE TABLE t 里的 ENGINE=,比调优 SQL 更先决。











