直接看 EXPLAIN
的 key
和 type
字段
这是最快速、最可靠的判断方式。只要
key列非
NULL,且
type不是
ALL或
index(全索引扫描),基本就走索引了。
type = ref或
eq_ref:走了二级索引或主键/唯一索引,效果好
type = range:走了索引范围扫描(如
WHERE age BETWEEN 25 AND 35)
type = ALL:没走索引,全表扫描——性能杀手,优先优化
key为空或显示
NULL:即使有索引,也没被选中
示例:
EXPLAIN SELECT * FROM users WHERE email = 'a@b.com';
如果返回中
key: idx_email且
type: ref,说明成功命中索引;若
key: NULL+
type: ALL,就得继续排查原因。
为什么有索引却没走?常见踩坑点
建了索引 ≠ 查询自动用索引。MySQL 优化器会基于成本估算决定是否使用,而“成本低”不等于“有索引就一定用”。
隐式类型转换:WHERE user_id = '123'(
user_id是
INT)→ 触发全表扫描,因为字符串转整数会让索引失效 对索引列用函数:
WHERE YEAR(create_time) = 2024→ 索引无法下推,改写为
create_time BETWEEN '2024-01-01' AND '2024-12-31'前导模糊匹配:
WHERE name LIKE '%abc'→ B+树无法从左匹配,索引失效;
LIKE 'abc%'可用 索引选择性太低:比如给只有两个值的
gender列建索引,优化器大概率放弃它 数据量极小(如几百行):优化器可能认为全表扫描比回表+索引查找更快
辅助验证:查索引是否存在 & 是否被统计
有时你以为有索引,其实根本没建,或者建错列了。用这两条命令交叉验证:
查索引定义:SHOW INDEX FROM users;确认
Column_name和
Key_name是否符合预期(比如你查
user_email上) 查索引统计信息(反映优化器“认知”):
SELECT * FROM mysql.innodb_index_stats WHERE database_name = 'your_db' AND table_name = 'users';若
n_diff_pfx01(基数)异常低,说明统计过期,可手动更新:
ANALYZE TABLE users;
进阶定位:用 optimizer_trace
看优化器真实决策
当
EXPLAIN显示没走索引,但你觉得“它应该走”,就可以打开优化器跟踪,看它到底怎么想的:
SET optimizer_trace="enabled=on"; SELECT * FROM users WHERE status = 1 AND created_at > '2025-01-01'; SELECT * FROM information_schema.optimizer_trace\G SET optimizer_trace="enabled=off";
重点关注
analyzing_range_alternatives和
using_index_for_order_by段落,里面会明确写出:“该索引成本为 123.45,全表扫描成本为 89.12,故选择后者”。这才是真正的原因,不是玄学。
注意:
optimizer_trace是会话级的,别忘了关;而且它只对当前会话生效,不能查别人跑的 SQL。
