or导致全表扫描,因优化器无法为on中or生成索引范围计划;应改用union all拆分join、exists替代或加索引约束。

JOIN条件里写OR会导致全表扫描
绝大多数数据库(MySQL、PostgreSQL、SQL Server)在ON子句中使用OR时,会放弃使用索引,转而对右表做全表扫描——哪怕左右表都有对应字段的索引。这是因为优化器无法为OR分支生成有效的索引范围扫描计划。
常见错误写法:
SELECT a.id, b.name FROM orders a LEFT JOIN users b ON a.user_id = b.id OR a.backup_user_id = b.id;
这种写法会让b表被反复扫描两次(甚至更多),性能急剧下降,尤其当users是大表时。
- 不是所有数据库都支持
OR条件下的索引下推(MySQL 8.0.13+ 对部分简单场景有改进,但不可依赖) -
OR在ON中比在WHERE中更危险:它直接影响连接算法选择,而非仅过滤结果 - 即使执行计划显示“Using index”,也可能只是覆盖索引扫描,而非高效查找
用UNION ALL拆分JOIN是最稳妥的替代方案
把含OR的单次JOIN,拆成多个独立JOIN再合并,能让每个分支走索引查找,且避免重复扫描。
等价改写示例:
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;
- 第二个
SELECT加WHERE a.user_id IS NULL是为了去重逻辑模拟(若业务允许重复,可省略该条件) - 必须用
UNION ALL而非UNION,否则会触发排序去重,抵消优化收益 - 如果两个JOIN条件涉及同一张大表,建议给
user_id和backup_user_id都建单独索引
用EXISTS替代OR-JOIN处理“匹配任一字段”场景
当目标只是判断是否存在匹配(而非取关联字段值),EXISTS通常比JOIN更轻量,也天然规避OR陷阱。
比如检查订单是否关联到有效用户:
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' );
- 每个
EXISTS子查询可独立利用users(id)索引,不产生中间结果集 - 数据库能对每个子查询提前终止(找到第一条即返回true),比JOIN更节省资源
- 注意:若需从
users取字段(如name),仍需JOIN,此时回到上一节的UNION ALL方案
警惕驱动表顺序与NULL值干扰
拆分后仍可能慢?检查这两点:
- 确保
orders是驱动表(出现在FROM最左),且user_id/backup_user_id上有索引;否则users可能被当作驱动表,导致反向全扫 -
OR条件常伴随NULL值逻辑,例如a.user_id = b.id OR a.backup_user_id = b.id在a.user_id为NULL时会命中所有users记录——这种语义本身就有性能隐患,应前置清洗或加非空约束
真正难的不是写出能跑的SQL,而是让每条JOIN只查它该查的那一行。OR条件天然破坏这个前提,所以拆、换、压——三选一,别硬扛。











