MySQL的index_merge性能常劣于复合索引,因其需多次随机IO扫描单列索引、内存归并主键流、无序回表导致高延迟;应优先建等值列前置的复合索引替代。

MySQL在多个单列索引存在时,常自动启用index_merge,但实际性能可能比走一个复合索引更差——这不是配置问题,而是优化器误判成本模型的结果。
为什么index_merge有时比全表扫描还慢
MySQL的index_merge本质是:分别用多个单列索引查出主键集合 → 归并(交集/并集)→ 回表取数据。这个过程隐含三重开销:
- 每个二级索引扫描都产生一次随机IO定位主键,而B+树非叶子节点缓存效果差,尤其当索引列选择性低时(如
status只有3个值) - 归并阶段需在内存中维护多个有序主键流,若结果集大,会触发磁盘临时表(
Using temporary; Using filesort) - 回表不是顺序读:合并后的主键无序,导致大量随机IO访问聚簇索引页,远比单个复合索引范围扫描的顺序IO昂贵
用EXPLAIN识别是否真在用index_merge
执行EXPLAIN SELECT ...后,重点看两处:
-
type字段为index_merge(不是range或ref) -
Extra字段含Using intersect(...)、Using union(...)或Using sort_union(...) - 对比
rows预估值:若index_merge的rows显著大于单个索引的rows,说明合并放大了扫描量
示例:EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'paid'; 若Extra显示Using intersect(idx_user_id,idx_status),且rows=8500,而EXPLAIN SELECT * FROM orders WHERE user_id = 123; 的rows=120,就该干预。
禁用index_merge的三种实操方式
不推荐全局关闭(影响其他合理场景),优先按查询粒度控制:
- 对单条SQL加
/*+ NO_INDEX_MERGE() */提示(MySQL 8.0.22+),如:SELECT /*+ NO_INDEX_MERGE() */ * FROM orders WHERE user_id = 123 AND status = 'paid'; - 用
FORCE INDEX强制走某个单列索引:SELECT * FROM orders FORCE INDEX (idx_user_id) WHERE user_id = 123 AND status = 'paid';(让优化器放弃合并,只用idx_user_id,status条件在回表后过滤) - 临时会话级关闭:
SET SESSION optimizer_switch='index_merge=off';,适用于批量修复慢查询脚本
真正治本:用复合索引替代单列索引组合
删除冗余单列索引,建覆盖查询条件的复合索引——这是最稳定、零配置依赖的解法:
- 按
WHERE条件中的等值列优先排序,范围列放最后。例如WHERE user_id = ? AND status = ? AND create_time > ?→ 建INDEX idx_user_status_time (user_id, status, create_time) - 若查询还带
SELECT *,且create_time之后还有常用列,可扩展为覆盖索引:INDEX idx_user_status_time_cover (user_id, status, create_time, order_id, total_amount) - 注意:已有
PRIMARY KEY(id)时,复合索引无需再包含id;InnoDB二级索引自带主键值,回表成本已隐含
删除旧索引前,用SELECT COUNT(*) FROM table WHERE col = ?验证单列索引是否被其他查询独占依赖——避免误删引发新慢查。
复合索引设计容易忽略的关键点:索引列顺序必须严格匹配查询中等值条件的出现顺序,且不能跳过中间列。比如WHERE status = ? AND create_time > ?无法用(user_id, status, create_time)索引,因为缺少user_id等值条件,最左前缀失效。











