MySQL中OR条件几乎必然导致索引失效,因优化器基于成本模型主动放弃索引;即便部分条件有索引,只要任一分支无索引或存在隐式转换,整个查询即退化为type:ALL;推荐用UNION ALL拆分并确保各子句有对应单列索引。

MySQL 中 OR 条件几乎必然导致索引失效——不是“可能不走”,而是只要任一分支无法走索引,整个查询就退化为 type: ALL。这不是配置问题,是优化器基于成本模型的主动放弃。
为什么 EXPLAIN 显示 key=NULL、type=ALL
优化器对 OR 极其保守:哪怕 name = 'a' 有索引,只要 age = 25 没索引,它就直接放弃所有索引。因为“一边查索引 + 一边全表扫 + 合并去重”的预估开销,高于一次顺序全表扫描。
-
EXPLAIN中key为NULL、type为ALL,基本等于宣告:这条OR没走任何索引 - 即使
name和age各有单列索引,index_merge也不会自动启用——它只在数据量小、选择度高、MySQL 8.0+ 且无复合索引干扰时才偶然触发 - 混用等值与范围(如
status = 'paid' OR created_at > '2025-01-01')会让index_merge更难生效,因合并逻辑要处理不同扫描模式
用 UNION ALL 拆分是最可控的修复方式
把“一个查询覆盖两棵树”改成“两个查询各走一棵树”,强制每个子句独立命中索引,效果稳定可预期。
- 每个子查询只能保留一个可索引条件,其余字段作为补充谓词加在
WHERE里,例如:SELECT id, name FROM t WHERE status = 'paid'UNION ALLSELECT id, name FROM t WHERE created_at > '2025-01-01' AND status != 'paid' - 必须显式写出列名,且顺序、类型、
NULL性完全一致,否则报错ERROR 1222 - 原查询带
LIMIT或ORDER BY?不能只在外层加,得分别在子查询里加;否则分页会漏数据、排序会错乱
什么时候不该拆,而该建复合索引
不是所有 OR 都适合 UNION。如果 OR 本质是同一维度的组合条件,建复合索引反而更干净、更少出错。
- 适用场景:
WHERE (status = 'paid' AND created_at > '2025-01-01') OR (status = 'refunded' AND created_at > '2025-01-01')→ 直接建(status, created_at)索引,type: range就能覆盖 - 慎用场景:字段值高度重叠(如
name和email都可能含'admin'),UNION去重逻辑会触发Using temporary和Using filesort,比原OR还慢 - 单字段多值
OR(如status = 'pending' OR status = 'processing')应优先用IN替代,语义清晰且天然走索引
联合索引对 OR 几乎无效,别抱幻想
有人误以为给 (name, age) 建联合索引就能让 name = ? OR age = ? 走索引——实际不行。联合索引只支持最左前缀匹配,OR 是并列条件,不是顺序过滤。
-
WHERE name = 'Alice' AND age = 25可走(name, age)索引 -
WHERE name = 'Alice' OR age = 25仍无法利用该联合索引,因为OR没有“最左”可依 - 若真要覆盖这类查询,更现实的做法是分别建
INDEX(name)和INDEX(age),再依赖优化器是否启用index_merge(但不可强依赖) - 隐式类型转换会让
OR雪上加霜:当任一分支触发隐式转换(比如user_id = 123而字段是VARCHAR),该分支必然不走索引;此时另一个分支即使类型正确,整个OR查询也大概率被优化器判为“不可靠”,直接弃用索引
真正容易被忽略的是:UNION ALL 拆分后,每个子查询能否走索引,完全取决于它自己有没有对应单列索引。没有 INDEX(age),光靠 (name, age) 联合索引,WHERE age = 25 这一分支仍是全表扫描。


















