or在join条件中导致索引失效,因优化器无法估算b.x = a.x or b.y = a.y的访问路径与结果集基数,即使b.x、b.y均有单列索引,也极少触发index merge;union all拆分需满足三前提:各分支字段有独立单列索引、左表重复引用、select字段严格一致。

OR在JOIN条件里为什么让优化器放弃索引
因为MySQL优化器无法为ON b.x = a.x OR b.y = a.y这种逻辑生成可预测的访问路径。它不是“不想用索引”,而是根本没法估算:如果先走b.x索引查出一批ID,再对每个ID去判断b.y = a.y是否成立,就得反复回表+判断;反过来也一样。更糟的是,这两个分支的结果集可能重叠,也可能不重叠——优化器没信心做准确基数估算,干脆退化为全表扫描或嵌套循环逐行判断。
典型信号是EXPLAIN里type显示ALL、key为NULL、Extra出现Using where,哪怕b.x和b.y各自都有单列索引。
Index Merge在JOIN中基本不起作用
即使b.x和b.y都建了索引,Index Merge也极少在JOIN场景下被触发。原因有三:
- Index Merge是单表扫描优化机制,设计初衷不面向JOIN中间结果集;
- MySQL 5.7及更早版本默认关闭
index_merge_intersection,且JOIN条件下几乎不会启用; - 即使5.7+启用了,优化器仍倾向认为“先JOIN再过滤”比“分别索引查ID再合并”成本更低——尤其当驱动表较大时。
所以别指望加两个单列索引就能自动救活OR JOIN;它和WHERE里的OR不是一回事。
UNION ALL拆分必须满足的硬性前提
把LEFT JOIN ... ON b.x = a.x OR b.y = a.y改成两个LEFT JOIN再UNION ALL,不是语法改写,而是语义重构。以下三点漏一个,结果就错或更慢:
- 每个子查询的
JOIN字段必须有独立索引:b.x和b.y不能共用一个复合索引,得是两个单列索引(或b.x有索引、b.y也有索引); - 左表必须重复引用两次,比如
FROM a LEFT JOIN b AS b1 ON ...和FROM a LEFT JOIN b AS b2 ON ...,否则LEFT JOIN语义丢失; -
SELECT字段列表必须严格一致:列数、顺序、类型、NULL属性,否则UNION ALL直接报错ERROR 1222。
最容易被忽略的NULL陷阱
即使你把OR拆成了UNION ALL,只要其中任一分支含IS NULL,而对应字段没建函数索引,那一支依然会全表扫描。例如:
SELECT a.*, b1.name FROM a LEFT JOIN b b1 ON b1.x = a.x UNION ALL SELECT a.*, b2.name FROM a LEFT JOIN b b2 ON b2.y IS NULL
第二支的b2.y IS NULL在多数MySQL版本中无法走普通索引(除非建了INDEX(y, id)这类覆盖索引)。这不是优化器懒,是B+树结构天然不擅长高效定位NULL值。
真正难的从来不是“怎么写SQL”,而是确认每一分支在真实数据分布下是否真的走索引——这一步必须用EXPLAIN挨个验证,不能靠推测。










