myisam的count(*)是o(1)元数据读取,innodb则必须按当前事务readview逐行判断mvcc可见性并全索引扫描,这是事务隔离语义决定的硬约束,无法绕开。

MyISAM 的 COUNT(*) 是 O(1) 操作,InnoDB 的 COUNT(*) 是带 MVCC 可见性判断的全索引扫描,本质不是“慢”,而是“必须这么算”——这是事务语义决定的,没法绕开。
MyISAM 的 COUNT(*) 本质是读磁盘元数据
MyISAM 在 .MYI 文件头固定偏移处存着一个叫 rec_count 的 4 字节整数,每次 INSERT/DELETE 都原子更新它。执行 SELECT COUNT(*) FROM t 时,MySQL 根本不读数据页、不走索引树、不判断可见性,只取这个值。
- 仅对无
WHERE条件生效;加了WHERE status = 1就退化为实际扫描,速度立刻掉下来 -
COUNT(*)、COUNT(1)、COUNT(pk)在 MyISAM 下执行计划和耗时完全一致,都是读同一个元数据 - 表刚被
OPTIMIZE TABLE或 MySQL 刚启动后,首次COUNT(*)可能触发一次全表扫描来重置计数器 - 不支持事务:事务 B 插入未提交时,事务 A 查不到这行 → 计数短暂不一致
InnoDB 的 COUNT(*) 必须逐行验证 MVCC 可见性
InnoDB 没有“当前有多少行”这个全局答案,因为每条记录都带 DB_TRX_ID 和 DB_ROLL_PTR,不同事务的 ReadView 不同,看到的行天然不同。它只能按当前事务上下文,调用 row_search_mvcc 一行行判断是否可见,再累加。
- 优化器会自动选体积最小的索引(比如
INDEX(status))遍历叶子节点,但仍是全索引扫描 - 如果表没二级索引,
EXPLAIN显示key: NULL,说明只能扫主键索引(即整行),I/O 爆炸 -
COUNT(*)、COUNT(1)、COUNT(id)性能几乎一样;COUNT(字段)若字段允许为 NULL,还要取值判空,反而更慢 - MySQL 8.0.18 存在已知 bug:buffer_pool 紧张时物理读激增,
COUNT(*)更容易超时
SHOW TABLE STATUS LIKE 't' 返回的 Rows 字段不能信
这个值在 InnoDB 下是采样估算结果,官方文档明确说误差可达 ±40%~50%。它基于随机选取约 10 个数据页,按平均行密度推算总数,不参与 MVCC 判断,也不保证事务一致性。
- 适合运维监控趋势(比如发现
table_rows突降 30%,提示可能误删) - 适合后台报表类需求(用户看到“约 2.3 万条”就足够)
- 绝不能用于分页总数、库存校验、审计统计等强一致场景——这些地方一旦误用,后端日志里全是“总数对不上”的排查工单
-
ANALYZE TABLE t可手动刷新估算,但会短暂锁表,别在高峰期跑
真正想让 InnoDB 的 COUNT(*) “快起来”,得放弃“实时精确”
没有银弹,只有折中。InnoDB 的一致性机制决定了它没法像 MyISAM 那样存一个固定值——这不是 bug,是设计使然。
- 高频精确计数:建一张
counter_table(table_name VARCHAR(64), cnt BIGINT),用触发器或应用层双写;注意触发器里别做复杂逻辑,否则拖慢主表写入 - 允许少量误差:缓存
COUNT(*)结果(比如 Redis),设置 5 分钟过期,配合写操作主动失效 - 别为了
COUNT(*)换引擎:MyISAM 不支持事务、崩溃后需手动修复、写操作锁整张表,代价远超查询提速收益
最常被忽略的是“可见性判断”本身不可绕过。哪怕你建了覆盖索引、开了并行扫描、调高 innodb_buffer_pool_size,只要业务要求结果对当前事务精确可见,InnoDB 就不得不一行行比对 ReadView。











