看到EXPLAIN中type为index_merge且key含多个索引,说明MySQL启用索引合并——需分别扫描、取交/并集、去重、排序、回表,性能常劣于单联合索引或全表扫描。

EXPLAIN看到index_merge就该警觉
只要EXPLAIN输出中type列为index_merge,且key字段列出多个索引(如key: idx_a,idx_b),说明MySQL正在走索引合并路径——这不是“用了多个索引”,而是启动了一套额外开销流程:分别扫描、取交集/并集、去重、排序、再回表。它常比单个联合索引慢,甚至比全表扫描还糟。
常见错误现象:
-
rows预估数没明显下降(比如单用idx_a预估10万行,合并后还是9.8万) -
Extra里没有Using intersect或Using union,只有空或Using where - 查询耗时随数据量增长陡增,而
SHOW PROFILE显示大量Copying to tmp table或Sorting result
确认index_merge是否真被启用
MySQL 8.0.19+ 默认关闭index_merge,靠optimizer_switch控制。不检查就调优,等于在关机状态下修发动机。
执行:
SELECT @@optimizer_switch;查看返回值中
index_merge=on是否生效。若为off,即使SQL写成WHERE a=1 AND b=2,也不会触发合并。
更关键的是,四个子开关可单独控制:index_merge_intersection、index_merge_union、index_merge_sort_union。比如你只想要交集行为,可设:
SET SESSION optimizer_switch='index_merge=on,index_merge_union=off,index_merge_sort_union=off';
用联合索引替代单列索引组合
绝大多数情况下,删掉idx_status和idx_created_at,建一个idx_status_created_at(顺序为(status, created_at)),性能提升立竿见影。原因很实在:B+树一次遍历就能定位,不用合并、去重、额外回表。
建联合索引时注意三点:
- 等值条件(
=、IN)放左边,范围条件(>、BETWEEN、LIKE 'abc%')放右边 - 高频过滤字段前置(比如
status区分度高、created_at区分度低,就把status放第一) - 避免冗余:已有
(a,b,c),再建(a,b)就是浪费,且增加写入负担
警惕index_merge引发的死锁风险
索引合并会同时持有多个二级索引上的next-key lock,加锁范围分散、顺序不一,极易与并发事务形成循环等待。尤其在FOR UPDATE或LOCK IN SHARE MODE场景下,死锁日志里常出现类似lock_mode X locks rec but not gap waiting和lock_mode X locks gap before rec insert intention waiting交错。
规避方法很简单:
- 禁用
index_merge(SET GLOBAL optimizer_switch='index_merge=off';) - 确保
WHERE条件能命中单一联合索引,让加锁集中在一棵B+树上 - 业务层控制并发粒度,避免对同一组
status+created_at范围高频更新
真正难的不是发现index_merge,而是意识到:它不是“多索引协同”,而是“多索引妥协”。一旦你开始依赖它,往往说明索引设计已偏离最左前缀和等值优先的基本逻辑。



















