EXPLAIN显示key只命中单列索引且rows远大于实际返回行数,说明其他WHERE条件未走索引,是联合索引改造的典型信号;应按高频等值条件优先、高区分度字段置左、范围/排序字段置右的原则重建联合索引。

看 EXPLAIN 的 key 和 rows 是否“只用上一个”
如果现有查询 EXPLAIN 显示 key 列只命中了某一个单列索引,但 rows 值远大于实际返回行数(比如查 10 行却扫了 5 万行),说明其他 WHERE 条件没走索引——这是联合索引最典型的改造信号。
常见表现:
-
WHERE user_id = ? AND status = ?只用了idx_user_id,status字段全表过滤 -
WHERE create_time > ? ORDER BY updated_at DESC出现Using filesort,且key是idx_create_time单列索引 - 多个单列索引共存,但查询从不单独用其中某一个(比如
idx_status几乎从不在慢日志里单独出现)
查慢日志里高频组合条件是否固定
联合索引不是为“可能一起查”建的,而是为“反复一起查”的模式建的。翻最近 3 天的慢查询日志或 performance_schema.events_statements_summary_by_digest,重点看:
- 哪些字段总在
WHERE中成对/成组出现(如tenant_id + biz_type、shop_id + order_status) - 是否同时带
ORDER BY或GROUP BY某些列(比如总查WHERE user_id = ? ORDER BY created_at DESC) - 有没有
IN或范围条件后紧跟等值条件(如WHERE a IN (1,2,3) AND b = 4)——这种适合把a放前、b放后
如果某组条件在慢日志中重复出现 ≥5 次/小时,且单列索引无法覆盖全部过滤逻辑,就值得合并。
验证选择性:别让低区分度字段当联合索引第一列
联合索引第一列必须扛得住初步过滤,否则整棵树都白建。用这个 SQL 快速估算:
SELECT COUNT(DISTINCT col) / COUNT(*) AS selectivity FROM table_name;
结果越接近 1 越好。典型陷阱:
-
gender字段(只有男/女)选作第一列 → 索引第一层就分裂成两支,后续列几乎无效 -
is_deleted(0/1)或status(枚举值少)放在最左 → 优化器大概率直接放弃该索引 - 正确做法:把
user_id、order_no、created_at这类高区分度字段放前面,status、type这类放后面
注意 MySQL 8.0+ 的 Index Skip Scan 不是“免死金牌”
MySQL 8.0.13+ 引入了 Index Skip Scan,看起来能绕过最左前缀限制,比如对 INDEX(a,b,c) 执行 WHERE b = ? AND c = ? 也能走索引。但它的生效条件极苛刻:
-
a列的COUNT(DISTINCT a)必须非常小(通常 ≤ 10~20) - 查询必须能被优化器重写为多个等值扫描(
a IN (v1,v2,...)),且总成本低于全表扫描 - 一旦
a的唯一值稍多(比如用户表的tenant_id有上百个),Skip Scan 就自动退化
所以别指望它救场。真正稳定的方案,还是老老实实按查询模式建联合索引,把高频等值条件列放在最左。
最容易被忽略的一点:联合索引生效不只看 WHERE,还要看 ORDER BY 和 SELECT 列——如果 SELECT * 且没有覆盖索引,即使 WHERE 走了联合索引,回表开销也可能抵消掉索引收益。


















