MyISAM的COUNT(*)极快,因其直接读磁盘元数据中的预存行数;InnoDB则必须逐行判断MVCC可见性并累加,导致全表扫描、性能随数据量下降。

MySQL 的 COUNT(*) 全表统计快慢,根本不在 SQL 写法,而在存储引擎——MyISAM 直接读磁盘值,InnoDB 必须一行行判可见性。
MyISAM 为什么 COUNT(*) 秒出结果
MyISAM 引擎在磁盘上单独维护了一个“总行数”字段,每次插入、删除都会原子更新它。执行 COUNT(*) 时,MySQL 直接从这个元数据里取值,不扫描任何数据页或索引树。
- 不加
WHERE条件时,COUNT(*)、COUNT(1)、COUNT(pk)效果完全一样,都是查这个缓存值 - 一旦加上
WHERE,哪怕只是WHERE status = 1,MyISAM 也得走索引或全表扫描,速度立刻掉下来 - MyISAM 不支持事务和行级锁,所以这个“总行数”是强一致的,但并发写入下可能丢失更新(不过对计数场景影响小)
InnoDB 为何必须逐行扫描
InnoDB 没有全局行数缓存,因为 MVCC 要求每个事务看到的“当前可见行数”可能不同。执行 COUNT(*) 时,它得遍历一棵索引树(通常是主键聚簇索引),对每一行调用 row_search_mvcc 判断是否对该事务可见,再累加——这本质是一次轻量级快照读。
- 优化器会选最小的索引树扫描(比如有
KEY(a)且a列很短,就可能比主键索引更快) - 即使表有千万行,只要索引能走
type=index(索引全扫描),也不触发磁盘随机 IO,但 CPU 和锁开销仍在 - 如果语句被其他长事务阻塞,或者自己带了
FOR UPDATE,COUNT(*)可能等锁,超时或卡住
SHOW TABLE STATUS 显示的 table_rows 能不能信
不能直接用于业务逻辑判断。这个值来自 InnoDB 的采样估算(采样页数默认约 10 页),官方文档明确说误差可达 40%–50%,尤其在数据分布不均、刚大批量删改后更不准。
- 查估算值:
SELECT table_rows FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = 'db' AND table_name = 't' - 手动刷新估算:
ANALYZE TABLE t,但会短暂锁表,别在高峰期跑 - 这个值只适合运维看趋势,比如监控“行数突降 30%”,提示可能误删;绝不用于分页总数或库存校验
真正要快,就得绕开 InnoDB 的一致性约束
如果你的应用能接受最终一致性(比如后台报表、用户看到的“约 2.3 万条”),那外部计数是唯一实用解法;若必须强一致,就得牺牲写性能换读性能。
- 写时维护:在业务代码里,每次
INSERT/DELETE后同步更新一张counter表的单行记录,用UPDATE counter SET cnt = cnt + 1 WHERE id = 1—— 这个操作极快,且与原表事务同属一个上下文 - Redis 计数快但有风险:Redis 重启未持久化、网络分区、写 MySQL 成功但 Redis 失败,都会导致不一致;没做双写幂等或补偿,基本等于埋雷
- 物化视图(如 MySQL 8.0+ 的
CREATE MATERIALIZED VIEW)目前还不支持自动刷新,实际不可用
最常被忽略的一点:很多人以为加个索引就能加速 COUNT(*),其实 InnoDB 下除非你建的是覆盖索引且字段极短,否则对纯 COUNT(*) 没用——它仍然得走主键索引判断可见性。真正的优化点永远在“要不要强一致”这个业务决策上,而不是 SQL 里多写个括号。











