or条件在连接查询中极易导致索引失效,因mysql优化器无法准确估算跨字段或混合or条件的选择性,常退化为全表扫描;唯一稳定方案是拆为union all,但须确保各分支独立走索引且无逻辑错误。

OR 条件在连接查询中极易导致索引失效,不是写法错,而是 MySQL 优化器面对跨字段或混合条件的 OR 时,几乎必然放弃使用索引,退为 type: ALL 全表扫描。唯一稳定可控的提速路径,是手动拆成 UNION ALL,但前提是每个分支都能独立走索引——否则只是把一次全表扫描,变成 N 次。
为什么 OR 在 JOIN 中更容易失效
连接查询里加 OR,相当于在驱动表或被驱动表的过滤条件中引入不确定性。优化器不仅要评估单表访问路径,还要估算连接后的中间结果集大小。一旦 OR 涉及多个字段(如 status = 'paid' OR customer_id = 123),或混入范围、函数、IS NULL 等,它就无法准确估算 selectivity,直接弃用索引。
- EXPLAIN 显示
key为空、rows_examined接近表总行数,就是典型信号 - 即使两个字段各自有单列索引,
OR也不会触发index_merge(尤其在 5.7 及更早版本) - 复合索引(如
(status, customer_id))对OR几乎无效——OR不满足最左前缀匹配逻辑
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))才能走索引,否则仍是全表扫描
什么时候不该硬拆 OR
不是所有 OR 都适合 UNION ALL。如果出现以下任一情况,优先检查索引设计或改用其他方式:
- 所有
OR都作用于同一字段(如id = 1 OR id = 2 OR id = 3)→ 直接改用IN(1,2,3),更简洁且稳定走索引 - 某个分支本身无法走索引(如
name LIKE '%abc'或DATE(created_at) = '2026-01-01')→ 拆了也是多次全表扫描,应先解决该分支的索引问题 - 表很小(
真正关键的从来不是“能不能拆”,而是拆完之后,每个 SELECT 的 EXPLAIN 里是否都出现了你期望的 key 和 type。没验证就上线,等于把性能问题从优化器手里,移交给了你自己。










