mysql跳过索引选择全表扫描是优化器基于成本模型的主动决策,认为顺序读聚簇索引比索引+回表更快;explain中type=all即确诊全表扫描,key=null仅表示本次未选中索引,非无索引。

MySQL跳过索引选择全表扫描,不是 bug,也不是配置错了,而是优化器基于成本模型算出来的“更优解”——它认为顺序读聚簇索引比走索引+回表更快。
EXPLAIN 中 type=ALL 就是明确放弃索引
只要 EXPLAIN 输出里 type 是 ALL,就代表这次查询一定在扫整张表的聚簇索引页。这不是警告,是确诊结果。
-
key字段为NULL≠ 没建索引,只表示这次没被选中 -
rows接近总行数(比如表有 200 万行,rows=1876432),说明索引基本没起作用 -
filtered值极低(如filtered=2.34),说明该条件几乎不筛数据,优化器自然不想用
Cardinality 过低会让优化器直接忽略索引
比如 status 列只有 'pending'、'paid'、'failed' 三个值,总行数 500 万,但 Cardinality=3,优化器估算查其中一种要回表上百万次,随机 IO 开销远超顺序扫描。
- 查当前值:
SHOW INDEX FROM orders WHERE Key_name = 'idx_status',看Cardinality列 - 强制刷新统计:
ANALYZE TABLE orders(注意锁表现象,避开高峰) - 长期建议开启持久化统计:
SET GLOBAL innodb_stats_persistent = ON+innodb_stats_auto_recalc = ON - 别单独为低区分度字段建索引;优先把它放在联合索引后位,例如
INDEX(user_id, status)
WHERE 条件写法会悄悄让索引失效
很多看似合理的写法,实际破坏了 B+ 树的物理区间定位能力,导致优化器无法使用索引。
- 对索引列用函数:
WHERE DATE(create_time) = '2026-06-01'→ 改成create_time >= '2026-06-01' AND create_time - 隐式类型转换:
phone VARCHAR(20)却写WHERE phone = 13800001111→ 改成WHERE phone = '13800001111' - 联合索引跳过最左前缀:
INDEX(a, b, c),却只写WHERE b = 1 AND c = 2 - 范围查询后接等值:
WHERE a = 1 AND b > 10 AND c = 'x'→c列索引失效
SELECT * 或 ORDER BY 未覆盖会放大回表代价
即使 WHERE 走了索引,SELECT * 或未覆盖的 ORDER BY 也会让优化器觉得“不如直接扫主键”。
- 索引是
INDEX(source_id, source_type),但语句是SELECT * FROM t WHERE source_id = ? ORDER BY id→id不在索引中,必须回表且额外排序 - 解决方向:用覆盖索引,把
SELECT需要的字段都包含进索引,或至少把ORDER BY字段带上 - 注意:MySQL 8.0.19+ 支持
EXPLAIN UPDATE,比先看EXPLAIN SELECT更准,因为执行路径可能不同
真正难处理的,是那些 Cardinality 失真 + 数据倾斜 + 统计信息滞后共同作用的场景——这时候光改 SQL 没用,得先让优化器“看清”数据分布。











