UNION ALL 替代 OR 通常比 Index Merge 更可靠可控,前提是各分支能独立走索引、无强交集且不带 GROUP BY;Index Merge 因限制多、开销大、易退化,常不如单索引或全表扫描。

直接结论:UNION ALL 替代 OR 通常比依赖 MySQL 的 Index Merge 更可靠、更可控,尤其在大表上;但前提是每个子查询都能独立命中索引,且你愿意承担语义微调和去重逻辑的额外工作。
为什么 Index Merge 经常不生效或很慢
MySQL 确实支持 index_merge(在 EXPLAIN 中看到 type: index_merge、Extra: Using union(...)),但它有硬性限制:
- 只对等值条件(
=、IN)有效,一旦混入范围(>、BETWEEN)、函数或隐式转换,整个合并就失效 - 即使两个字段都有单列索引,优化器也可能因预估成本高而放弃合并,直接走全表扫描(
type: ALL) - 合并过程需读取多个索引 B+ 树、归并结果、去重,实际 I/O 和 CPU 开销可能高于单索引 + 全表过滤
- MySQL 5.7 及更早版本对
index_merge支持较弱,8.0 虽有改进,但依然不稳定
UNION ALL 拆分的实操前提
不是所有 OR 都适合拆。必须同时满足以下三点,否则改了反而更慢:
- 每个 OR 分支对应一个**独立可走索引的条件**(如
status = 'paid'且status有索引;user_id = 123且user_id有索引) - 各分支之间**业务语义无强交集**,或你能接受少量重复(用
UNION ALL),或能加精确排他条件(如AND status != 'paid') - 原查询 **不带 GROUP BY / HAVING / DISTINCT**;若带,需在外层再聚合,代价陡增,慎用
例如:SELECT id FROM orders WHERE status = 'shipped' OR user_id = 456 → 可拆;但 WHERE (a = 1 AND b = 2) OR (a = 1 AND c = 3) 若已有复合索引 (a,b,c),MySQL 可能走 range,无需拆。
怎么写才不翻车:参数、顺序与陷阱
常见错误不是语法错,而是执行计划没变或结果不对:
- 每个子查询的
SELECT列数、类型、顺序必须完全一致,否则报错ERROR 1222 - 别漏掉共用条件:如果原查询有
AND deleted = 0,它得复制到每个子查询的WHERE中,不能只写在外层 -
LIMIT不能只放最后:想分页?每个子查询自己加LIMIT 100,再外层ORDER BY ... LIMIT 20 OFFSET 0,否则可能漏数据 - 排序必须在外层统一做:
UNION ALL不保证顺序,ORDER BY必须套在最外层SELECT上 - 注意 NULL 行处理:原
OR可能包含IS NULL,而UNION ALL子查询里得显式写出来,否则丢失
验证效果只能靠 EXPLAIN,不是靠“看起来像”
改完必须跑 EXPLAIN FORMAT=TREE(MySQL 8.0+)或至少 EXPLAIN 看每条子查询:
- 确认每个子查询的
key非空,type是ref、const或range,不是ALL或index - 检查
rows是否明显下降(比如从 100w 降到几千) - 对比执行时间:用
BENCHMARK()或多次SELECT SQL_NO_CACHE排除缓存干扰 - 特别留意
Extra字段:出现Using temporary或Using filesort外层排序时,说明数据量大,排序本身成了瓶颈
真正容易被忽略的是:UNION ALL 后的结果集大小可能远超预期(尤其多分支重叠时),导致网络传输、应用内存、后续 JOIN 都承压——这不是 SQL 层能解决的问题,得结合业务数据分布一起看。


















