UNION ALL 拆分是最可靠解法,可绕过优化器对 ON 中 OR 的失效判断、避免右表全表扫描;需确保各分支走索引、复制过滤条件、列一致、用 UNION ALL 而非 UNION,并验证执行计划与语义等价。

直接拆成 UNION ALL 是最可靠、见效最快的解法,能绕过优化器对 ON 中 OR 的失效判断,避免右表全表扫描。
为什么 ON 里写 OR 会让 JOIN 变慢
数据库优化器无法为 ON a.x = b.id OR a.y = b.id 这类跨字段条件生成有效索引驱动路径。它不尝试 index_merge(尤其在 JOIN 场景下基本禁用),而是直接对右表做全表扫描——哪怕 b.id 上明明有索引。
常见信号:EXPLAIN 中右表的 type 是 ALL 或 index,key 为 NULL,rows 接近右表总行数。
用 UNION ALL 拆分 JOIN 的实操要点
把一个含 OR 的 LEFT JOIN 拆成多个独立 LEFT JOIN,再合并:
- 每个分支只保留一个连接条件,确保
a.user_id = b.id和a.backup_user_id = b.id各自能走b.id索引(或对应字段的单列索引) - 若原查询是
LEFT JOIN,第二个分支必须加WHERE a.user_id IS NULL,否则会漏掉仅匹配backup_user_id的行 - 所有公共过滤条件(如
status = 'active'、时间范围)必须复制进每个子查询的WHERE,不能只放外层 -
SELECT列列表必须完全一致:列数、顺序、类型、NULL 属性;否则报错ERROR 1222 - 必须用
UNION ALL,不是UNION——后者触发隐式排序和去重,开销更大
示例:
SELECT a.id, b.name FROM orders a LEFT JOIN users b ON a.user_id = b.id WHERE b.status = 'active' UNION ALL SELECT a.id, b.name FROM orders a LEFT JOIN users b ON a.backup_user_id = b.id WHERE a.user_id IS NULL AND b.status = 'active';
容易被忽略的索引与逻辑陷阱
拆分后性能没提升?大概率卡在这三点:
- 每个分支单独执行
EXPLAIN SELECT ... WHERE,确认type是ref或range,且key显示命中了你期望的索引 - 如果两个分支可能返回同一行(比如
user_id = 123和backup_user_id = 123同时成立),UNION ALL会产生重复数据,业务是否允许需确认 - 原查询含
GROUP BY、LIMIT或ORDER BY?这些不能简单套在外层——LIMIT必须下推到每个子查询,ORDER BY得放在最外层再做
真正难的不是改写语法,而是验证每个分支是否真的走了索引、NULL 处理是否符合语义、以及拆分后结果集是否和原逻辑等价——光换写法不查执行计划,等于没动。


















