MySQL优化器遇OR分支索引失效时主动弃用索引,因全表扫描成本低于索引+全扫合并;单字段OR改IN,跨字段OR用UNION ALL拆分,复合条件优先建联合索引,避免隐式转换与动态NULL判断。

EXPLAIN里key为NULL、type为ALL,基本等于宣告索引被弃用
这不是偶然现象,而是MySQL优化器基于成本模型的主动决策:只要OR任一分支无法走索引(比如name = 'a'有索引但age = 25没索引),它就直接放弃所有索引。因为“一边查索引 + 一边全表扫 + 合并去重”的预估开销,通常高于一次顺序全表扫描。
即使两边字段各自有单列索引,index_merge也极不稳定——它只在数据量小、选择度高、MySQL 8.0+且无复合索引干扰时才可能触发;混用等值与范围条件(如status = 'paid' OR created_at > '2025-01-01')会让index_merge更难生效。
IN比单字段OR更安全,UNION ALL比跨字段OR更可控
单字段多值OR(如status = 'A' OR status = 'B')应直接改写为status IN ('A', 'B'),这是MySQL原生优化过的路径,稳定走索引。
跨字段OR(如name = 'Alice' OR city = 'Chicago')则推荐拆成UNION ALL:
- 每个子查询必须显式写出相同列名、类型和NULL性,否则报
ERROR 1222 - 第二条要补上排除重叠的谓词,例如
AND name != 'Alice',除非业务能确认两条结果天然不重合 - 原查询带
LIMIT或ORDER BY?必须分别加在子查询里,否则分页漏数据、排序错乱
不是所有OR都该拆,有些更适合建复合索引
如果OR本质是同一维度的组合条件,建复合索引反而更干净。例如:
WHERE (status = 'paid' AND created_at > '2025-01-01') OR (status = 'refunded' AND created_at > '2025-01-01') → 直接建INDEX(status, created_at),type: range就能覆盖。
慎用UNION ALL的场景:
- 字段值高度重叠(如
name和email都含'admin'),去重逻辑会触发Using temporary和Using filesort,比原OR还慢 - 低Cardinality字段(如
is_deleted只有两个值)单独参与OR,建索引本身意义不大,应放联合索引后位
动态参数拼OR(如#{userId} IS NULL)是隐形杀手
这类写法看似灵活,实则让优化器彻底失能。例如:
WHERE (user_id = #{userId} OR #{userId} IS NULL) AND (status = #{status} OR #{status} IS NULL)
问题在于:OR #{xxx} IS NULL引入了不可预测的执行路径,优化器无法对任意参数组合生成稳定执行计划。它既不能提前剪枝,也无法判断哪些分支可走索引。
真正可行的替代方案是服务端动态拼SQL,或用CASE WHEN配合函数索引(MySQL 8.0+),而不是把判断逻辑全塞进WHERE里。
最常被忽略的一点:哪怕你给所有OR字段都建了索引,只要其中任何一个字段存在隐式类型转换(比如phone VARCHAR(20)却写WHERE phone = 13800001111),整条OR就立即退化为type: ALL。


















