OR不破坏索引但优化器常弃用,因需索引覆盖全部条件;两边均有单列索引才可能启用index_merge;UNION ALL替代OR更可控,确保各分支独立走索引。

OR 本身不“破坏”索引,但 MySQL 优化器在面对 OR 时,往往放弃走索引——不是它不能,而是它算下来觉得不划算。
OR 中只要有一个分支没索引,整个查询大概率全表扫描
常见错误现象:EXPLAIN 显示 type=ALL、key=NULL,哪怕你明明给 name 建了索引,但写成 WHERE name = 'Alice' OR age = 25,而 age 没索引,结果就是:
- 优化器想:“我用
name索引捞出一批 ID,再回表取数据;可age = 25这部分还得扫全表 —— 那不如直接全表扫一遍,省得来回折腾。” - 最终决策:放弃所有索引,
type=ALL
这和“有没有索引”无关,只和“能不能靠索引覆盖全部条件”有关。
-
name有索引 +age没索引 → 全表扫描 -
name有索引 +age有索引 → 不一定走索引(见下条)
OR 两边都有索引,也不等于能用上 —— index_merge 不是默认开箱即用
MySQL 确实支持 index_merge(索引合并),但它启用门槛高:
- 必须所有
OR分支字段都单独建了单列索引(联合索引不行) - 优化器估算
index_merge成本低于全表扫描,才会选它 -
EXPLAIN中看到type=index_merge才算真启用了,否则还是退化
容易被忽略的点:
-
INDEX(name, age)是联合索引,对name = ? OR age = ?完全无效 —— 因为OR不满足最左前缀匹配逻辑 - 即使开了
index_merge,它本质是:分别查两个索引 → 内存合并 ID 集合 → 回表。比单索引查询多一次合并+去重,性能并不一定更好
用 UNION ALL 替代 OR 才是最可控的解法
UNION ALL 把逻辑拆成独立子查询,每个都能走自己的索引,且不依赖优化器“猜对”。
示例:
-- 不推荐(不可控) SELECT * FROM user WHERE name = 'Alice' OR age = 25; -- 推荐(每个分支独立走索引) SELECT * FROM user WHERE name = 'Alice' UNION ALL SELECT * FROM user WHERE age = 25;
注意:
- 用
UNION ALL而非UNION,除非你明确需要去重(UNION多一次排序去重,成本更高) - 如果要
ORDER BY或LIMIT整体结果,必须外包一层:SELECT * FROM ( SELECT * FROM user WHERE name = 'Alice' UNION ALL SELECT * FROM user WHERE age = 25 ) t ORDER BY id LIMIT 10;
- 无法在
WHERE中引用外层别名,这是语法限制,不是 bug
真正难处理的,是那些你以为“两边都有索引就安全了”的场景 —— 实际上线后 EXPLAIN 一跑,发现还是 type=ALL。这时候别急着调优器参数,先检查是否误把联合索引当万能钥匙,或者漏掉了隐式类型转换(比如 id = '123' 但 id 是 VARCHAR),这些都会让 index_merge 直接失效。

















