innodb的count(*)必须走索引树遍历,因其无全局行数缓存,mvcc导致各事务可见行数不同,只能按当前readview逐行判断可见性;优化器选择最窄索引(如key idx_status)以减少io,而非固定扫主键索引。

为什么InnoDB的COUNT(*)必须走索引树遍历,而不是查个缓存
因为InnoDB根本就没有“全局行数”这个缓存值可查。MyISAM能秒回COUNT(*),是它把精确行数固化在表头;而InnoDB为支持MVCC,同一时刻不同事务看到的行数可能完全不同——比如事务A还没提交INSERT,事务B就查不到那行。数据库没法给出一个“对所有事务都正确”的数字,只能按当前事务快照,一行行判断可见性。
COUNT(*)到底扫的是哪棵树:主键索引还是二级索引
优化器会选最窄的索引树来扫,不一定是主键。比如表有KEY idx_status (status),且status是TINYINT,那它比BIGINT主键窄得多,COUNT(*)就会走这个二级索引。原因很直接:二级索引叶子节点只存主键值,体积小、IO少;而主键索引叶子节点存整行数据,代价高。但注意:如果所有索引都包含大字段(比如前缀过长的VARCHAR),优化器也可能退回到扫主键索引。
COUNT(*)和COUNT(1)、COUNT(id)执行路径真的一样吗
逻辑上等价,但执行细节有差异:
-
COUNT(*):Server层不做NULL判断,引擎返回一行就+1,开销最小 -
COUNT(1):优化器几乎总把它重写成COUNT(*),行为一致 -
COUNT(id)(id为主键):引擎仍扫索引树,但Server层要取id值再判是否NULL——虽然主键不可能为NULL,这步仍是冗余检查 -
COUNT(non_null_col):如果该列定义为NOT NULL且有索引,效果接近COUNT(*);否则可能触发回表或全表扫描
为什么加了WHERE条件后COUNT反而可能变慢
加WHERE不等于自动提速,关键看能否走索引+是否覆盖:
- WHERE条件走不到索引(比如
LIKE '%abc'),仍是全表扫描,还多了一层过滤判断 - WHERE走了索引但非覆盖(比如
WHERE status = 1,但status索引不包含其他字段),引擎需回表确认每行是否满足,COUNT过程反而更重 - 真正快的情况是:WHERE条件命中
覆盖索引,且该索引足够窄——这时COUNT(*)实际只扫索引树子集,比无条件扫整棵树还省
最易被忽略的一点:哪怕你建了完美索引,只要事务中存在长未提交的查询或大量undo log,COUNT(*)仍可能因MVCC版本链遍历变慢——这不是SQL写法问题,是InnoDB并发模型本身的代价。











