union all拆分是最可靠、见效最快的解法,能绕过优化器对or条件的失效判断,避免全表扫描和嵌套循环,确保各分支走独立索引,显著提升性能。

直接拆成 UNION ALL 是最可靠、见效最快的解法,几乎所有主流数据库(MySQL、Oracle、PostgreSQL、Spark SQL)都适用,且能绕过优化器对 OR 条件的失效判断。
为什么 OR 在 JOIN 条件里会让查询变慢
数据库优化器很难为 ON b.x = a.x OR b.y = a.y 这类条件生成高效执行计划。它通常放弃使用索引,转而对被驱动表做全表扫描;更糟的是,某些引擎(如 MySQL 5.7、Spark SQL 1.x)甚至会退化为嵌套循环 + 每行重复判断,逻辑读和 CPU 消耗呈数量级上升。你看到的 type: ALL 或 Extra: Using where 就是典型信号。
用 UNION ALL 拆分 JOIN 的实操要点
把一个带 OR 的 LEFT JOIN 拆成两个独立 LEFT JOIN,再用 UNION ALL 合并结果——关键不是“语法等价”,而是“语义可控”:
- 每个分支只保留一个连接条件,确保
b.x = a.x和b.y = a.y各走自己的索引(b.x、b.y必须有单列或前导索引) - 若原始是
LEFT JOIN,拆分后仍需保持左表所有行:两个子查询都从左表出发,分别LEFT JOIN右表的不同别名(如b1、b2),再UNION ALL - 注意字段对齐:
SELECT a.*, b1.col1, b1.col2和SELECT a.*, b2.col1, b2.col2中的列顺序、类型、NULL 性必须一致,否则UNION ALL会报错或隐式转换 - 不加
DISTINCT:除非业务明确要求去重,否则用UNION ALL;UNION会触发排序+去重,反而拖慢速度
Spark SQL 中 OR 导致数天运行的修复案例
某 Spark SQL 作业对 24 亿行 A 表和 30 万行 B 表做 LEFT JOIN,条件是 bb.ip = aa.ip1 OR bb.ip = aa.ip2,原写法卡在 ShuffleHashJoin 阶段数天不结束。修复方式是:
sqlContext.sql("CACHE TABLE all AS SELECT name, ip1, ip2 FROM table_A WHERE ip1 IS NOT NULL OR ip2 IS NOT NULL")
sqlContext.sql("SELECT name, ip1 AS ip FROM all").registerTempTable("a1")
sqlContext.sql("SELECT name, ip2 AS ip FROM all").registerTempTable("a2")
sqlContext.sql("SELECT * FROM a1 UNION ALL SELECT * FROM a2").registerTempTable("a_flat")
-- 后续只需简单 INNER JOIN:bb.ip = aa.ip
核心是把 OR 从 JOIN 条件里“提出来”,变成数据预处理阶段的逻辑拆分,让真正 JOIN 时只剩等值匹配。
容易被忽略的索引与 NULL 陷阱
即使用了 UNION ALL,如果没配好索引,性能照样上不去:
-
UNION ALL拆分后的每个分支,其JOIN字段必须单独建索引。例如拆成b.x = a.x和b.y = a.y,就要有INDEX(x)和INDEX(y),缺一不可 - 如果关联字段允许
NULL,MySQL 可能跳过索引(尤其LEFT JOIN右表字段为NULL时)。可考虑加WHERE b.x IS NOT NULL显式过滤,或业务层保证非空 - Oracle 场景下,可加
/*+ USE_CONCAT */提示辅助优化器自动展开OR,但不如手动UNION ALL稳定;且该提示对LEFT JOIN效果有限,仍需配合索引
真正卡住性能的,往往不是 JOIN 本身,而是 OR 让索引彻底失效后,数据库被迫在十亿行里逐行比对。拆分 + 索引,才是直击要害的组合拳。











