explain中type=all或key=null表明查询未走索引,需排查优化器弃用原因而非索引是否存在;重点观察type、key、rows和extra字段,并检查函数使用、类型转换、联合索引最左匹配及like模糊方式等索引失效场景。

看到 EXPLAIN 输出里 type=ALL 或 key=NULL,基本可以确定这条查询没走索引——这不是偶然,是优化器明确放弃了索引。排查重点不是“有没有建”,而是“为什么不用”。
用 EXPLAIN 看清执行计划真实状态
这是第一道也是最关键的门槛。别只看 possible_keys 里有没有名字,要盯住实际生效的字段:
-
type是 ALL?直接确认全表扫描,不用犹豫 -
key是 NULL?说明优化器没选任何索引,哪怕列上有索引也白搭 -
rows值远大于你预期返回行数?说明扫描范围失控 -
Extra出现Using filesort或Using temporary?排序/分组开销已在路上,但不等于索引失效,需分开处理
示例:EXPLAIN SELECT * FROM orders WHERE status = 'paid' 返回 type=ALL,哪怕 status 上建了索引,也要继续往下查原因。
检查 WHERE 条件是否让索引“隐形”
索引存在 ≠ 能用。常见让索引当场失效的操作有:
- 对索引列用函数:
WHERE YEAR(create_time) = 2023→ 改成WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31' - 隐式类型转换:
user_id是BIGINT,却传字符串'123'→ 改成123或显式转类型 - 联合索引跳过最左列:
INDEX(a, b, c),只写WHERE b = ?→ 无效;必须含a才可能触发 - LIKE 左模糊:
name LIKE '%abc'→ B+Tree 无法定位前缀,索引失效;右模糊'abc%'可用
注意:key=NULL 不代表索引不存在,只是这次优化器觉得走索引更慢——可能是统计信息过期,也可能是数据太“扁平”(比如 status 只有 3 个值,选择性低于 5%)。
验证索引选择性与统计信息是否可信
优化器靠统计信息估算代价。如果它误判,就会绕过索引:
- 运行
ANALYZE TABLE orders强制刷新统计信息,再EXPLAIN对比 - 算真实选择性:
SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders,结果 - 低区分度字段更适合做联合索引后缀,比如
INDEX(user_id, status),而非独立索引 - 小表(比如几千行)或查询返回超 20% 行数时,优化器主动放弃索引是合理行为,不是 bug
别迷信 FORCE INDEX。它能压着优化器走索引,但掩盖了设计缺陷——上线前必须实测 SELECT 耗时,而不是只看 EXPLAIN 的 rows。
结合慢查询日志抓真凶 SQL
单看表或索引没用,真正拖垮性能的是高频执行的“坏 SQL”。sys.schema_table_statistics 里的 rows_full_scanned 只告诉你哪张表被扫得多,但不告诉你哪条语句干的:
- 先确保开启慢查:
slow_query_log = ON,long_query_time = 1(别设为 0) - 用
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log找出耗时 Top 10 - 对每条慢 SQL 执行
EXPLAIN FORMAT=TREE your_sql,重点看:type是否为ALL、key是否为NULL、是否有<not used></not>提示 - 特别警惕:WHERE 字段有索引,ORDER BY 字段没覆盖 → 仍会
Using filesort,甚至触发回表全扫
最常被忽略的一点:SELECT COUNT(*) 和 SELECT ... LIMIT 1 同样可能全表扫描。别只盯着带复杂条件的查询,基础聚合和存在性判断也得进慢日志筛一遍。











