索引合并不是万能药:MySQL同时用两个索引,为什么比不用还慢?
|
zhenglin
2026年9月9日 14:47
本文热度 169
|
某电商订单系统,开发人员给status和create_time分别建了单列索引。查询条件很简单:
SELECT * FROM orders
WHERE status = 'PAID' AND create_time > '2026-09-01'
ORDER BY create_time DESC LIMIT 20;
EXPLAIN一看,possible_keys列显示两个索引,优化器选择了索引合并(Index Merge) ,Extra列出现Using intersect(idx_status, idx_create_time)。
开发人员很高兴:“两个索引都用上了,优化器真智能!”
但实际跑起来,这条SQL在2000万数据的表上要3.8秒。加了个FORCE INDEX(idx_create_time)强制走单个索引,反而降到了0.3秒。
优化器“智能”地做了错误的决定。
今天把索引合并这件事彻底拆开,讲清楚它是什么、什么时候该用、什么时候千万别用。
一、索引合并的三种类型
MySQL的索引合并优化(Index Merge Optimization)在5.0时代就引入了,官方文档把它描述为一种“优化策略”,但不是“最优策略”。
1. Intersection(交集合并)
同时使用多个索引,取结果集的交集。
SELECT * FROM orders
WHERE user_id = 12345 AND status = 'PAID';
如果user_id和status各自有单列索引,优化器可能同时扫描两个索引,然后取交集。2. Union(并集合并)
同时使用多个索引,取结果集的并集。
SELECT * FROM orders
WHERE user_id = 12345 OR status = 'PAID';
这是最危险的一种——两个索引的结果集取并集,需要去重、排序,代价极高。
3. Sort-Union(排序并集合并)
先对索引扫描结果排序,再去重合并。比普通Union多了排序步骤,代价更高。
一个设计良好的复合索引通常比索引合并更高效,因为单次索引查找就能定位数据,避免了合并开销。
二、索引合并的代价到底在哪?
索引合并看起来“利用了多个索引”,但代价隐藏在三个地方:
代价1:多次索引扫描 + 结果集合并
索引合并需要扫描多个索引树,然后把结果集在内存中做交集或并集运算。如果每个索引扫描返回的数据量都很大,合并操作本身的开销可能超过全表扫描。
代价2:随机I/O放大
索引扫描返回的是主键值(二级索引),然后需要回表读取完整行数据。索引合并意味着多次回表——每次索引扫描都要回表一次,I/O次数成倍增加。
代价3:基数估算偏差
优化器决定是否使用索引合并,依赖于基数估算(Cardinality Estimation)。如果统计信息过期或数据分布倾斜,优化器可能错误地认为索引合并很快,但实际上慢得要命。
三、3个真实踩坑场景
坑1:OR条件导致索引合并UNION,代价远超预期
SELECT * FROM orders
WHERE user_id = 12345 OR create_time > '2026-01-01';
优化器可能选择索引合并UNION——分别扫描idx_user_id和idx_create_time,然后合并去重。
但如果user_id=12345有10万行,create_time > '2026-01-01'有50万行,合并去重要处理60万行数据——比全表扫描还慢。
坑2:索引合并的基数估算偏差
MySQL优化器基于统计信息做决策。当统计信息过期时,优化器可能低估某个索引返回的行数,从而错误地选择索引合并方案。
坑3:多个单列索引 vs 一个复合索引
很多人有个误区:给每个查询条件列都建一个单列索引,让优化器自己去“合并”。
但复合索引通常比索引合并更高效——索引合并需要扫描多个索引、合并结果集、去重、排序;复合索引一次扫描就能定位到目标行。
-- 不推荐:两个单列索引让优化器去合并
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
-- 推荐:一个复合索引覆盖查询
CREATE INDEX idx_user_status ON orders(user_id, status);
四、什么时候该用索引合并,什么时候该用复合索引?
| 场景 | 推荐方案 | 原因 |
|---|
| 查询条件是AND,各条件选择性都很高 | 复合索引 | 一次索引查找定位,无合并开销 |
| 查询条件是AND,但其中一个条件选择性极低 | 单列索引+过滤 | 复合索引收益有限,索引合并代价高 |
| 查询条件是OR,各条件选择性都很高 | 索引合并UNION可接受 | 无法用单个复合索引覆盖OR条件 |
| 查询条件是OR,但结果集很大 | 改写SQL或用UNION ALL | 避免索引合并的去重和排序开销 |
| 查询条件经常变化,无法预建复合索引 | 索引合并作为兜底 | 聊胜于无,但需监控性能 |
五、怎么判断优化器是否选错了?
方法一:对比执行计划
分别用FORCE INDEX强制走单个索引和让优化器自由选择,对比响应时间。
方法二:查看EXPLAIN的Extra列
-
Using intersect(...) → 交集合并,通常AND条件触发
-
Using union(...) → 并集合并,通常OR条件触发
-
Using sort_union(...) → 排序并集合并,代价最高
方法三:用EXPLAIN ANALYZE看实际行数
EXPLAIN ANALYZE会输出每个步骤的实际执行行数。如果actual rows远大于优化器估算的rows,说明基数估算有偏差。
方法四:使用OPTIMIZER_TRACE
开启optimizer_trace,可以看到优化器在索引合并和其他方案之间的代价对比,精确了解优化器为什么选了索引合并。
六、小结
索引合并是优化器的“兜底方案”,不是“首选方案”。能用复合索引解决的,优先用复合索引。如果EXPLAIN里出现了Using intersect或Using union,先确认索引合并的真实代价——很多时候,一个设计良好的复合索引比索引合并快一个数量级。索引合并的出现往往暗示你的索引设计还有优化空间。
阅读原文:点击这里
该文章在 2026/9/9 14:47:43 编辑过