MySQL优化器弃用索引是因成本估算认为全表扫描更优,主因包括数据倾斜、低区分度、统计过期、联合索引不满足最左前缀及范围查询后列失效;需结合EXPLAIN、COUNT验证与ANALYZE TABLE优化。

MySQL优化器忽略索引,不是“不认索引”,而是它算出来走索引比全表扫描还慢——这个判断在数据倾斜、低区分度或统计过期时经常出错。
WHERE条件匹配大量行时,优化器主动弃用索引
当某列值高度重复(比如 status 只有 'draft'/'published'/'archived' 三种),且查询 WHERE status != 'draft' 会返回 85% 的行,优化器估算:走索引要先扫索引树再回表取数据,总 I/O 比直接顺序读整张表还高。于是选 type: ALL。
- 典型现象:
EXPLAIN显示possible_keys有索引,但key为NULL,rows接近表总行数 - 验证方法:用
SELECT COUNT(*) FROM t WHERE status != 'draft'看实际比例;若 > ~20%,索引大概率被跳过 - 不要硬改 SQL,先看是否真需要这个查询——有时加缓存或预聚合更治本
联合索引的最左前缀未满足,导致“逻辑不可用”
建了 INDEX idx_user_status_created (user_id, status, created_at),但查询写成 WHERE status = 'paid' AND created_at > '2024-01-01',优化器发现无法从索引头开始定位,该索引对当前谓词“不可用”,哪怕 possible_keys 列出来了,也不会进候选集。
- 关键点:不是“失效”,是“不匹配”——B+Tree 只能从最左列开始做等值或范围查找
- 如果必须查
status和created_at,且user_id不在条件里,就得另建覆盖这两个字段的索引,或改用FORCE INDEX(但需确认该索引实际能支撑查询) - 注意:范围查询(如
>)之后的列无法用于索引查找,WHERE user_id = 123 AND status > 'a' AND created_at = '2024-01-01'中,created_at就不会走索引查找
统计信息过期或采样不准,让成本模型失真
执行过大批量 DELETE/INSERT 后未运行 ANALYZE TABLE orders,优化器仍按旧统计估算:比如以为 WHERE created_at > '2026-09-01' 只返回 100 行,实际已插入 50 万新订单——它选了索引,但 rows 严重低估,最终执行极慢;反过来,若统计显示某值占比 99%,它就直接跳过索引。
-
SHOW INDEX FROM orders查看Cardinality是否合理;对比SELECT COUNT(DISTINCT created_at) / COUNT(*) FROM orders看实际区分度 -
ANALYZE TABLE是低成本操作,线上可随时执行;5.7+ 支持innodb_stats_auto_recalc = ON自动触发,但大表仍建议手动补算 - 某些场景下,即使统计最新,优化器对范围条件的选择性估算仍有偏差(尤其时间字段带大量 NULL 或空值)
真正难处理的不是“为什么没走索引”,而是“为什么它觉得不该走”——这背后是采样、模型、数据分布三者耦合的结果。临时用 FORCE INDEX 能压住症状,但若不验证该索引是否真能减少 rows 和避免回表,很容易把性能问题从查询层推到存储层。


















