myisam的count(*)是o(1)读元数据,innodb则需按readview逐行判断可见性并遍历最小索引,本质差异源于mvcc导致无法预存统一行数,故性能差距根本在于引擎设计而非sql写法。

COUNT(*) 在 InnoDB 和 MyISAM 中耗时差别大,根本不是 SQL 写法或配置问题,而是两种引擎对“行数”这个概念的定义和实现完全不同——MyISAM 存的是一个数,InnoDB 算的是一个快照。
MyISAM 的 COUNT(*) 本质是读磁盘元数据
MyISAM 把精确行数固化在 .MYI 文件头的一个 4 字节整数里,每次 INSERT/DELETE 都原子更新它。执行 SELECT COUNT(*) FROM t 时,MySQL 根本不碰数据页,只读那个固定偏移量的值。
- 无
WHERE条件时,时间复杂度是O(1),10 行和 10 亿行耗时几乎一样 - 一旦加了
WHERE status = 1,MyISAM 也得走索引扫描,速度立刻掉下来 - 这个“快”是有代价的:不支持事务、崩溃后需手动修复、写操作锁整张表
- 别误以为建表没指定引擎就默认用 MyISAM——MySQL 5.7+ 默认是 InnoDB
InnoDB 的 COUNT(*) 必须按 ReadView 逐行判断可见性
InnoDB 没有全局统一的行数,因为 MVCC 下不同事务看到的“可见行”天然不同。它只能对当前事务的 ReadView,遍历索引树、调用 row_search_mvcc 判断每行是否可见,再累加。
- 优化器会选最小的索引(比如
INDEX(status))扫描,但仍是全索引遍历,I/O 和 CPU 开销都高 - 表越大、buffer pool 越紧张,物理读越多,超时风险越高
-
COUNT(*)、COUNT(1)、COUNT(pk)性能几乎没差别,别信“用 1 更快”的说法 -
EXPLAIN SELECT COUNT(*) FROM t显示type: index,说明确实在扫索引,不是优化器偷懒
SHOW TABLE STATUS LIKE 't' 返回的 Rows 字段不能信
这个值在 MyISAM 下是精确的,在 InnoDB 下只是采样估算(默认扫描约 10 个数据页),官方文档明确说误差可达 ±40%–50%。
- 适合运维看趋势(比如发现
table_rows突降 30%,提示可能误删) - 绝不适用于分页总数、库存校验、审计统计等强一致场景
-
ANALYZE TABLE t可手动刷新估算,但会短暂锁表,别在高峰期跑 - 查它比
COUNT(*)快得多,但你得清楚自己要的是“大概多少”还是“到底多少”
真正想让 COUNT(*) 快起来,得放弃“实时精确”
没有银弹,只有折中。InnoDB 的一致性机制决定了它没法像 MyISAM 那样存一个固定值——这不是 bug,是设计使然。
- 高频精确计数:建
counter_table,用触发器或应用层双写,注意触发器里别做复杂逻辑,否则拖慢主表写入 - 允许少量误差:缓存
COUNT(*)结果(比如 Redis),设置 5 分钟过期,配合写操作主动失效 - 带条件统计(如
COUNT(*) WHERE status=1):给status加覆盖索引,让 InnoDB 只扫二级索引即可 - 最常被忽略的一点:很多接口里的
COUNT(*)其实根本不需要精确值——比如“是否还有下一页”,用LIMIT 11查 11 条,够 11 条就显示“下一页”,比算总数快两个数量级
COUNT(*),而是没想清楚“这个数到底用来干什么”。











