MyISAM的COUNT(*)是O(1)操作,因其在元数据中直接存储精确行数;InnoDB则需遍历主键索引树逐行判断可见性,时间复杂度为O(n),且受MVCC影响无法缓存固定值。

为什么 COUNT(*) 在 MyISAM 上快得离谱,InnoDB 却越来越慢
因为 MyISAM 在表头元数据里直接存着精确行数,COUNT(*) 是个 O(1) 操作;而 InnoDB 每次都得扫一遍主键索引树,实际是遍历所有叶子节点——数据量从 100 万涨到 10 亿,耗时几乎线性增长。
- MyISAM 不受 MVCC 影响,不用判断每行对当前事务是否可见
- InnoDB 必须逐行检查隐藏字段
DB_TRX_ID和DB_ROLL_PTR,确认该行是否“属于”当前快照 - 如果表没主键,InnoDB 会用内部生成的
row_id构建聚集索引,但扫描逻辑不变,照样慢 - 执行
EXPLAIN SELECT COUNT(*) FROM t时,InnoDB 显示type: index,说明确实在走索引全扫
别迷信 “MyISAM 查询更快”,大数据下它可能翻车
有真实案例:11 亿记录的订单表,换引擎测试发现 InnoDB 的 WHERE id BETWEEN ? AND ? 查询比 MyISAM 快 4 倍以上——不是因为 InnoDB 突然变快,而是 MyISAM 的表级锁和非聚集索引在高并发范围查询中成了瓶颈。
- MyISAM 表级锁:一个
UPDATE正在跑,所有后续读写全排队,QPS 断崖下跌 - MyISAM 回表要两次 IO(先查索引得地址,再按地址读磁盘),InnoDB 聚集索引主键查询一次定位即得整行
- MyISAM 全文检索虽早支持,但中文分词弱、不支持事务上下文,现代业务基本不敢用
- 超过 5000 万行后,MyISAM 的 .MYI 索引文件容易碎片化,
OPTIMIZE TABLE会锁表数小时
想让 InnoDB 的 COUNT(*) 快起来,绕不开这三件事
没有银弹,但有可落地的折中方案。核心思路是:不硬刚全表扫描,改用近似值、缓存或预计算。
- 用
SHOW TABLE STATUS LIKE 't'查Rows字段——这是 InnoDB 的估算值(基于采样),误差可能达 ±40%,但毫秒级返回 - 建专用计数器表,比如
counter_table (table_name VARCHAR(64), cnt BIGINT),配合INSERT/UPDATE/DELETE触发器维护,注意触发器里不能写复杂逻辑,否则拖慢主表写入 - 如果只统计带条件的行(如
COUNT(*) WHERE status=1),给status加覆盖索引:INDEX(status)或INDEX(status, id),让 InnoDB 只扫二级索引即可
什么时候真该考虑 MyISAM?现实里其实极少
除非你正在维护一个只读、无事务、每天凌晨才 LOAD DATA INFILE 一次的报表归档库,且 DBA 明确告诉你磁盘 IO 已成瓶颈、又不愿加 Redis 缓存层——否则别碰 MyISAM。
- MySQL 5.5 起默认引擎就是 InnoDB,不是历史遗留,是经过大规模生产验证的选择
- MyISAM 崩溃后需手动运行
myisamchk,而 InnoDB 崩溃恢复由redo log自动完成,重启即可用 - 哪怕只做
COUNT(*),只要业务存在任何写操作(哪怕一小时一次),MyISAM 的锁等待和修复风险就远超那点查询省下的毫秒
真正卡住性能的,往往不是引擎选型本身,而是没意识到 COUNT(*) 在 InnoDB 里本质是个“隐式全索引扫描”——把它当轻量操作用,迟早撞上数据量拐点。


















