index merge引发死锁的根本原因是优化器动态选择多索引路径导致加锁顺序不一致,形成循环等待;解法是用联合索引或force index统一访问路径,而非调整隔离级别或关闭优化器。

Index Merge不是“策略错误”,而是优化器在多索引可选时的动态决策结果;它本身没有错,但不可控——这才是问题根源。
为什么EXPLAIN显示type: index_merge却查得慢或死锁?
MySQL 5.1+ 启用 index_merge 后,当 WHERE 中多个字段各自有单列索引(如 WHERE status = 0 AND trans_id = 'xxx'),优化器可能选择先走 idx_status 再回表,另一事务却先走 idx_trans_id,导致加锁顺序相反,形成死锁。性能差则常因合并开销大、回表频繁、统计信息不准引发误判。
- 死锁不看隔离级别:
READ COMMITTED和REPEATABLE READ都会触发,调低级别无用 - 执行计划不稳定:同一 SQL 在不同数据分布下可能走全表扫描、单索引或
index_merge,无法预测 -
Extra字段出现Using union(...)或Using intersect(...)是明确信号
用联合索引替代单列索引,优先级更高且路径唯一
删掉 KEY idx_status (status) 和 KEY idx_trans_id (trans_id),新建覆盖查询条件的联合索引,让优化器“没得选”:
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
DROP INDEX idx_status ON t; DROP INDEX idx_trans_id ON t; CREATE INDEX idx_trans_id_status ON t (trans_id, status);
- 如果
trans_id唯一性高(如订单号),把它放前面,能更快定位行 - 如果查询常带
status范围条件(如status IN (0,1)),则把status放前,但需注意最左前缀匹配 - 联合索引同时解决回表和
index_merge争抢问题,比FORCE INDEX更可持续
FORCE INDEX 可临时绕过,但不能当长期方案
在 UPDATE/SELECT 中显式指定唯一入口索引,强制跳过 index_merge 决策:
UPDATE t SET status = 1 WHERE status = 0 AND trans_id = 'xxx' FORCE INDEX (idx_trans_id);
- 仅对当前语句生效,DDL 或应用重启后仍需重复加,维护成本高
- 若被
FORCE的索引后续被删或失效,SQL 直接报错ERROR 1176 - 不适合 ORM 自动生成的 SQL 场景,多数框架不支持注入
FORCE INDEX
禁用 index_merge 全局开关的风险点
仅在确认无任何业务依赖该特性时才考虑:
SET GLOBAL optimizer_switch='index_merge=off,index_merge_union=off,index_merge_sort_union=off,index_merge_intersection=off';
- 该设置对已建立连接无效,新会话才生效,线上滚动生效难控制
- 影响所有含 OR / 多等值条件的查询,可能让原本走
index_merge的高效查询退化为全表扫描 - Percona Server 或 MySQL 8.0+ 的统计信息反馈机制更敏感,关了反而更容易误判
真正关键的不是“怎么关”,而是“哪些查询实际需要它”——绝大多数 OLTP 场景,联合索引 + 显式谓词设计,比依赖优化器自动合并更可靠。










