ON子句中使用OR会导致数据库放弃索引、触发右表全表扫描,因优化器无法为多字段OR生成有效索引范围扫描;推荐用UNION ALL拆分JOIN或EXISTS替代,但需确保索引覆盖并验证执行计划。

JOIN 的 ON 子句里写 OR,基本等于主动放弃索引——MySQL、PostgreSQL、SQL Server 都会因此对右表做全表扫描,哪怕字段上明明有索引。
为什么 ON 中的 OR 会导致全表扫描
数据库优化器无法为 OR 分支生成有效的索引范围扫描计划。它不能同时用两个不同字段(比如 a.user_id 和 a.backup_user_id)去驱动一次索引查找;更糟的是,LEFT JOIN 场景下还可能重复扫描右表多次。
- 执行计划里即使显示
Using index,也大概率是覆盖索引扫描,不是你想要的点查 -
OR在ON中比在WHERE中更危险:它直接干扰连接算法选择,不只是过滤结果 - MySQL 8.0.13+ 对极简
OR有部分下推支持,但业务 SQL 很少满足那些限制条件,不可依赖
用 UNION ALL 拆分 JOIN 是最稳妥的替代方案
把一个含 OR 的 JOIN 拆成多个独立 JOIN,每个分支都能走索引查找,且右表只被扫描一次(按需)。
- 必须用
UNION ALL,不是UNION——后者会触发隐式排序和去重,反而拖慢性能 - 第二个分支要加
WHERE a.user_id IS NULL,模拟LEFT JOIN的“补 NULL”逻辑(若业务允许重复,可省略) - 确保
user_id和backup_user_id字段各自有单列索引,或至少有能覆盖查询的复合索引
SELECT a.id, b.name FROM orders a LEFT JOIN users b ON a.user_id = b.id 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;
用 EXISTS 替代 OR-JOIN 处理“是否存在匹配”场景
如果目标只是判断关联记录存在与否(比如校验用户是否有效),EXISTS 比 JOIN 更轻量,也天然绕过 OR 陷阱。
- 每个
EXISTS子查询可独立走users(id)索引,且支持提前终止(找到第一条就返回 true) - 不会产生中间结果集,内存和 CPU 开销都更低
- 但注意:
EXISTS只适合布尔判断;一旦需要从右表取字段(如b.name),就得回到UNION ALL方案
SELECT a.* FROM orders a WHERE EXISTS ( SELECT 1 FROM users b WHERE b.id = a.user_id AND b.status = 'active' ) OR EXISTS ( SELECT 1 FROM users b WHERE b.id = a.backup_user_id AND b.status = 'active' );
真正容易被忽略的点是:拆分后各分支的驱动表顺序、NULL 值处理逻辑、以及索引是否真的覆盖了所有查询路径——光改写 SQL 不够,得看执行计划里每条 SELECT 是否真走了 type=ref 或 type=eq_ref。

















