EXPLAIN中key为NULL、type为ALL表明该OR查询未使用任何索引,因优化器保守评估认为全表扫描成本低于索引合并或混合扫描,故直接退化为全表扫描。

MySQL中OR条件几乎必然导致索引失效——不是“可能”,而是只要任一分支无法走索引,整个WHERE就退化为type: ALL。
EXPLAIN里key是NULL、type是ALL意味着什么
这基本等于宣告:这条OR没走任何索引。优化器对OR极其保守——哪怕name = 'a'有索引,只要age = 25没索引,它就直接放弃所有索引。原因不是bug,而是成本模型算出来的结果:一边查索引 + 一边全表扫 + 合并去重的预估开销,高于一次顺序全表扫描。
- 即使
name和age各自有单列索引,index_merge也极不稳定,不能当解决方案依赖 -
index_merge只在数据量小、选择度高、MySQL 8.0+、且无复合索引干扰时才偶然触发 - 混用等值与范围(如
status = 'paid' OR created_at > '2025-01-01')会让index_merge更难生效
什么时候该用UNION ALL拆分
把“一个查询覆盖两棵树”改成“两个查询各走一棵树”,是最可控的修复方式。每个子查询必须能独立命中索引,且结果集语义等价。
- 必须显式写出列名,且顺序、类型、
NULL性完全一致,否则报错ERROR 1222 - 每个子查询只能保留一个可索引条件,其余字段作为补充谓词加在
WHERE里,例如:SELECT id, name FROM t WHERE status = 'paid'UNION ALLSELECT id, name FROM t WHERE created_at > '2025-01-01' AND status != 'paid' - 原查询带
LIMIT或ORDER BY?不能只在外层加,得分别在子查询里加;否则分页会漏数据、排序会错乱 - 如果业务能确认两个条件天然不重叠(比如
status = 'paid'和created_at > ...有强约束),第二条里的AND status != 'paid'可删——去掉后更快,且不引入Using temporary
什么时候不该拆,而该建复合索引
不是所有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原生支持且高效
真正容易被忽略的是:你看到EXPLAIN里key为NULL,第一反应不该是“怎么让index_merge生效”,而应立刻检查是否真需要OR——很多时候,它只是业务逻辑没收敛、查询条件没归一化的副产品。


















