在写SQL时,为了图方便经常会把多个等值或范围判断用OR连起来,比如按照不同状态、不同渠道或不同时间段取数。当表数据量变大、OR条件变多后,这类语句很容易变成全表扫描,索引基本派不上用场。把OR条件改写成UNION或UNION ALL,是提升索引效率的实用办法。

为什么大量OR会让索引失效
优化器在处理带有多个OR的where条件时,如果涉及不同列或复杂表达式,往往难以把每个OR分支都映射到独立索引,于是选择全表扫描。即使某个列有索引,其他分支无法走索引也会拉低整体效率。
典型慢查询示例
下面这张订单表在status、channel、user_id上分别有索引,但查询却很慢:
SELECT order_id, amount FROM orders WHERE status = 1 OR channel = 'app' OR user_id = 10086;
这个语句里三个条件分布在不同列,数据库通常不愿拆成多棵索引树扫描,直接全表扫描反而被认为更省事。
用UNION改写提升索引效率
核心思路是把每个OR分支拆成独立子查询,让每个子查询都能使用自己的索引,再用UNION或UNION ALL合并结果。如果确定结果无重复或重复可接受,优先用UNION ALL避免去重开销。
改写后的写法
SELECT order_id, amount FROM orders WHERE status = 1 UNION ALL SELECT order_id, amount FROM orders WHERE channel = 'app' UNION ALL SELECT order_id, amount FROM orders WHERE user_id = 10086;
改写后,每个SELECT都能命中对应列的索引,执行计划从全表扫描变成三次索引范围扫描,再做结果合并,效率明显提升。
改写时的注意点
- 如果业务要求结果唯一,使用UNION做去重;否则用UNION ALL减少开销。
- 子查询选择的列要保持一致,避免类型隐式转换导致索引失效。
- 对于同一列上的多个OR等值,例如 code = 'A' OR code = 'B',直接用IN替代即可,不必拆UNION。
同一列用IN更简单
-- 不推荐
SELECT * FROM orders WHERE code = 'A' OR code = 'B' OR code = 'C';
-- 推荐
SELECT * FROM orders WHERE code IN ('A', 'B', 'C');
验证执行计划
不论怎么改写,都要用EXPLAIN查看执行计划,确认type从ALL变成range或ref,并且key列显示用到了预期索引。只有实际命中索引,UNION改写才算真正生效。
把OR换成UNION ALL不是万能药,但当OR跨多列且各有索引时,它往往是最直接有效的优化手段。
| 写法 | 扫描方式 | 索引利用 |
|---|---|---|
| 多列OR | 全表扫描 | 差 |
| UNION ALL拆分 | 多次索引扫描 | 好 |
遇到历史慢查询里堆满OR的SQL,先想清楚每个条件能否独立走索引,再动手用UNION改写,通常能拿到立竿见影的效果。