on中写or会导致优化器放弃右表索引,因无法为多字段or生成有效索引范围扫描计划,进而触发右表全表扫描;应改用union all拆分join并验证各分支是否真走索引。

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 很少满足那些限制条件,不可依赖 - 即使两个字段各自有单列索引,
OR也不会触发index_merge(尤其在 5.7 及更早版本)
UNION ALL 拆分后必须验证的三件事
别急着改 SQL,先确认这三点,否则改完更慢:
- 对每个
OR分支单独执行EXPLAIN SELECT * FROM t WHERE ...,确保type是ref或range,且key显示命中对应索引 - 检查各分支是否天然无交集:比如
status = 'paid'和status = 'refunded'不会重叠;但status = 'paid'和customer_id = 123可能返回同一行,需业务确认是否允许重复 - 原查询若含
GROUP BY、HAVING或ORDER BY/LIMIT,不能简单拆——LIMIT必须下推到每个子查询,ORDER BY得放在外层再做
改写时最容易漏掉的细节
语法等价 ≠ 行为等价。以下几点漏一个,结果就错或变慢:
- 所有公共条件(如
deleted = 0 AND created_at >= '2025-01-01')必须复制进每个SELECT的WHERE子句,不能只写在外层 - 每个子查询的
SELECT列表必须完全一致:列数、顺序、类型、NULL属性,否则报错ERROR 1222 (21000) - 用
UNION ALL,不是UNION;除非业务强要求去重,否则UNION会强制排序 + 临时表,开销远超原OR -
IS NULL分支需单独建函数索引(如INDEX(col, id))才能走索引,否则仍是全表扫描
真正容易被忽略的点是:拆分后各分支的驱动表顺序、NULL 值处理逻辑、以及索引是否真的覆盖了所有查询路径——光改写 SQL 不够,得看执行计划里每条 SELECT 是否真走索引。










