MySQL中OR条件几乎必然导致索引失效,因优化器遇任一分支不可索引即弃整体索引,退化为type: ALL;应优先用IN替代同字段多值OR,或用UNION ALL拆分跨字段OR并确保各子查询独立走索引。

MySQL中OR条件几乎必然导致索引失效——不是“可能”,而是只要任一分支无法走索引,整个WHERE就退化为type: ALL;即使两边都有索引,index_merge也极不稳定,不能当解决方案依赖。
为什么EXPLAIN里key是NULL、type是ALL
优化器对OR极其保守:哪怕name = 'a'有索引,只要age = 25没索引,它就直接放弃所有索引,因为“一边查索引 + 一边全表扫 + 合并去重”的开销预估高于一次顺序全表扫描。这不是bug,是成本模型算出来的结果。
-
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 - 如果业务能确认
status = 'paid'和created_at > ...天然不重叠(比如状态和时间有业务约束),第二条里的AND status != 'paid'可删——去掉后UNION ALL更快,且不引入Using temporary - 原查询带
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 = 'A' OR status = 'B')应直接改写为status IN ('A', 'B'),MySQL对IN的优化远好于OR
验证是否真的修复了
改完不看EXPLAIN等于没改。关键看三件事:
-
type是否变成ref、range或index_merge(后者少见但可接受),绝不能再是ALL -
key列是否显示实际使用的索引名,而不是NULL -
rows预估扫描行数是否显著下降——如果从百万级降到几千,才算真正见效
真正容易被忽略的是语义等价性:加AND status != 'paid'看似合理,但如果status允许NULL,这个条件会漏掉status IS NULL的行;UNION ALL不查重,但业务是否真能容忍重复,得拉真实数据对一遍。


















