用 EXPLAIN
看执行计划,这是最直接有效的判断方式
MySQL 不会主动告诉你“用了哪个索引”,但
EXPLAIN会把优化器的决策摊开给你看。在你要分析的
SELECT语句前加
EXPLAIN,比如:
EXPLAIN SELECT * FROM users WHERE email = 'a@b.com';
重点关注这四列:
type:值为
ALL→ 全表扫描,没走索引;
const/
eq_ref/
ref/
range→ 通常走了索引
key:非
NULL(如显示
idx_email)→ 实际使用了该索引
rows:预估扫描行数,远小于表总行数才说明索引过滤有效;接近总数可能已失效
Extra:出现
Using filesort或
Using temporary→ 即使走了索引,排序或分组仍可能回表或额外开销
注意:
key非空 ≠ 高效,必须结合
type和
rows综合判断。
常见索引失效场景,一查就中招
很多 SQL 表面看着合理,其实根本没走索引。典型例子包括:
对索引列做隐式类型转换:WHERE phone = 13800138000(
phone是
VARCHAR),应写成
WHERE phone = '13800138000'在索引列上使用函数:
WHERE YEAR(create_time) = 2023→ 改用范围查询:
WHERE create_time >= '2023-01-01' AND create_time模糊查询以
%开头:
LIKE '%abc'→ 无法利用索引,可考虑全文索引或倒排结构 联合索引未满足最左前缀:
INDEX(a, b, c),但只查
WHERE b = 1→ 不走索引;必须从
a开始才有机会命中 索引列参与计算:
WHERE score + 10 > 90→ 改为
WHERE score > 80
辅助验证:查索引是否存在、是否被真正用过
光看
EXPLAIN还不够,得确认基础条件是否成立: 用
SHOW INDEX FROM users;查表上有没有对应索引,以及索引列顺序是否匹配查询条件 用
performance_schema.table_io_waits_summary_by_index_usage(MySQL 5.6+)看某索引实际被调用频次 查询
sys.schema_unused_indexes(MySQL 5.7+ sys schema)识别长期零使用的冗余索引 开启慢查询日志(
slow_query_log=ON),设
long_query_time=1,捕获那些因未走索引而变慢的语句
这些不是替代
EXPLAIN的手段,而是帮你定位“为什么明明建了索引却没被选中”。
进阶调试:用 OPTIMIZER_TRACE
看优化器怎么想的
当
EXPLAIN显示没走索引,但你确信它“应该走”,就可以启用优化器跟踪:
SET optimizer_trace="enabled=on"; SELECT * FROM users WHERE age > 30; SELECT * FROM information_schema.optimizer_trace; SET optimizer_trace="enabled=off";
返回的 JSON 里会明确写出:“为什么放弃索引 idx_age?因为预估扫描行数占比太高,成本高于全表扫描”。这种细节,
EXPLAIN不会告诉你。
真正容易被忽略的是:索引是否“被选中”和是否“高效”是两回事——
type=range+
rows=10万的查询,跟
type=ref+
rows=2的性能差距,可能比走不走索引还大。
