type=ALL说明全表扫描,索引未生效;key非NULL不等于索引被用,需看type是否为range/ref及以上;常见失效原因包括函数操作、隐式转换、联合索引未满足最左前缀等。

EXPLAIN 显示 type=ALL 却以为索引生效了
这是最典型的误判:看到 key 列非 NULL 就以为索引被用了,但 type=ALL 说明 MySQL 还是全表扫描。真正有效的索引扫描至少得是 range、ref 或更优类型。
常见原因:
-
WHERE条件中对索引列用了函数或表达式,比如WHERE YEAR(create_time) = 2023—— 索引失效 - 隐式类型转换,比如字段是
VARCHAR,却传入数字:WHERE user_id = 123(而user_id实际是字符串) - 联合索引最左前缀没满足,比如索引是
(a, b, c),但查询只写了WHERE b = ? AND c = ?
验证方法:用 EXPLAIN FORMAT=TRADITIONAL 查看 possible_keys 和 key 是否一致,再比对 type 和 rows —— 如果 rows 接近表总行数,基本等于没走索引。
索引列顺序写反导致 ORDER BY 无法复用索引
联合索引既要支撑 WHERE 过滤,又要支撑 ORDER BY,顺序就非常关键。MySQL 只能按索引定义的顺序“连续使用”索引列做排序。
比如查询:SELECT * FROM orders WHERE status = 'paid' ORDER BY created_at DESC
如果建的是 (created_at, status),那 ORDER BY 能用上,但 WHERE status = ... 就只能走 range 扫描甚至失效;反过来建 (status, created_at),过滤和排序才能一起生效。
注意点:
-
ORDER BY方向必须和索引列方向一致(都是ASC或都是DESC),MySQL 8.0+ 支持混合方向,但 5.7 不支持 - 如果
SELECT中有SELECT *且索引不是覆盖索引,会触发回表,rows可能低估实际 I/O 开销
EXPLAIN 的 rows 值不准,别直接当真实扫描行数
rows 是优化器估算值,基于统计信息(SHOW INDEX FROM table 查 Cardinality),不是执行时的真实值。尤其在数据分布不均、统计过期时,误差可能极大。
典型表现:
- 明明加了索引,
rows却比全表还大,优化器干脆放弃索引,选了ALL - 刚批量插入数据,
ANALYZE TABLE没跑,统计还是旧的,rows严重偏低
实操建议:
- 执行
ANALYZE TABLE <code>orders强制更新统计信息 - 用
SELECT COUNT(*)+ 实际WHERE条件验证真实命中行数,和EXPLAIN rows对比 - 观察
Handler_read_*状态变量(如Handler_read_next),比rows更反映真实索引遍历行为
索引优化后 QPS 没涨,可能是缓存掩盖了问题
如果查询之前跑过很多次,结果还在 Query Cache(5.7 默认关)或 InnoDB Buffer Pool 里,哪怕没走索引,也可能秒出结果。这时候看 EXPLAIN 没用,得看真实执行耗时。
排查步骤:
- 加
SQL_NO_CACHE(仅限开启 Query Cache 时)或先清缓存:FLUSH TABLES;(谨慎) - 用
SYS.STATISTICS或performance_schema.events_statements_history_long查真实timer_wait - 对比开启/关闭索引前后,
SHOW PROFILE FOR QUERY N中的Copying to tmp table、Sorting result时间占比
真正卡住的往往不是索引是否命中,而是排序、临时表、大字段 SELECT 导致的内存溢出或磁盘落临时文件 —— 这些在 EXPLAIN 里根本看不到。



















