怎么看 EXPLAIN
输出里的 key
和 possible_keys
不一致?
MySQL 选索引不是“哪个能用就用哪个”,而是基于成本估算。即使
possible_keys列出多个索引,
key只显示最终选中的那个——它可能不是你预期的最“宽”或“最左”的索引。
常见误导:看到
possible_keys包含
idx_user_status和
idx_user_created_at,但
key是后者,就以为“没走状态索引”。其实是因为查询中
created_at > '2024-01-01'返回行数远少于
status = 'active'(比如后者占全表 60%),优化器判定用时间范围索引更便宜。 用
EXPLAIN FORMAT=JSON查看
used_range_access和
rows预估,比只看
key更可靠 如果
possible_keys为空但实际有索引,检查字段是否用了函数(如
WHERE YEAR(created_at) = 2024)或隐式类型转换(如字符串列和数字比较)
key_len值比索引定义长度小,说明只用了前缀(比如
VARCHAR(255)字段建了前缀索引
INDEX(name(50)),
key_len就是 50 × 字符编码字节数)
复合索引的最左匹配到底卡在哪儿?
“最左前缀原则”不是语法限制,而是 B+ 树检索路径决定的:必须从索引第一列开始连续提供等值条件,才能高效定位数据页。一旦中间出现范围查询(
>、
BETWEEN、
LIKE 'abc%'),后续列就无法用于索引查找,只能用于过滤(
Extra: Using index condition)。
SELECT * FROM orders
WHERE user_id = 123
AND status IN ('paid', 'shipped')
AND created_at > '2024-05-01';对这个查询,
INDEX(user_id, status, created_at)比
INDEX(user_id, created_at, status)更优——因为
status IN算等值匹配(MySQL 8.0+ 支持多值等值查找),而
created_at >是范围,放最后才不打断前缀匹配。 排序需求也要纳入索引设计:
ORDER BY created_at DESC跟在等值条件后,可被同一复合索引覆盖,避免
Using filesort
SELECT *时,覆盖索引失效,但若只查索引列(如
SELECT user_id, status),
INDEX(user_id, status, created_at)就能完全避免回表 不要为每个
WHERE字段单独建索引,优先合并:三个单列索引不如一个设计合理的三列复合索引(除非查询模式差异极大)
type
是 range
还是 ref
,为什么影响这么大?
type直接反映扫描方式:
ref表示通过索引等值查找定位到具体数据页(快),
range表示按索引顺序扫描一段区间(慢,尤其区间大时)。两者性能差距常达数量级。
典型陷阱:明明写了
WHERE category_id = 5 AND price BETWEEN 100 AND 500,
EXPLAIN却显示
type: range。原因往往是索引顺序错了——如果建的是
INDEX(price, category_id),优化器只能先按
price范围扫描,再在结果里过滤
category_id,变成
range;改成
INDEX(category_id, price)后,
category_id = 5定位到子树,再在该子树内做
price范围扫描,
type就变成
ref。
type: ALL或
index是全表/全索引扫描,必须干预;
type: const或
eq_ref是理想状态(主键/唯一索引等值查找)
Extra: Using index表示覆盖索引,
Using where表示存储引擎返回数据后 Server 层还要过滤——后者意味着索引没包含所有
WHERE条件列 当
rows预估值远大于实际返回行数,可能是统计信息过期,执行
ANALYZE TABLE table_name更新
什么时候该删索引?
索引不是越多越好。写多读少的表,每个额外索引都会拖慢
INSERT/
UPDATE/
DELETE,还占用内存和磁盘。重点删这三类: 重复索引:
INDEX(a,b)和
INDEX(a)同时存在,后者无意义 低效索引:
SHOW INDEX FROM table_name查
Cardinality,如果某列唯一值极少(如
is_deleted TINYINT只有 0/1),且从不单独用于查询,这个单列索引基本无效 长期未用索引:开启
performance_schema,查
sys.schema_unused_indexes视图(MySQL 5.7+),但注意它只统计 Server 层执行计划记录,不捕获直接命中缓存的查询
调整索引本质是平衡读写开销。一个被高频查询用到的复合索引,即使让单条
UPDATE慢 5ms,也通常值得;但一个三个月没被
EXPLAIN选中的索引,留着只是浪费。
