mysql中or条件几乎必然导致索引失效,因优化器评估成本后放弃所有索引而全表扫描;union all替代是目前最可控的解法,但每个子句须有对应单列索引支撑。

MySQL 中 OR 条件几乎必然导致索引失效——不是“可能不走”,而是只要任一分支无法走索引,整个查询就退化为 type: ALL,优化器直接放弃所有索引。
EXPLAIN 显示 key=NULL、type=ALL 就是索引彻底没用
这是最直接的信号。优化器看到 WHERE name = 'Alice' OR city = 'Beijing' 这类语句时,并不会想“先用 name 索引查一批,再用 city 索引查一批,最后合并”。它算的是总成本:
- 如果
city没索引,那无论如何都得全表扫一遍才能凑齐所有结果 - 既然都要扫全表,那还费劲去走
name索引干啥?纯属多此一举 - 哪怕
name有主键或唯一索引,只要另一边不可索引,它也常被跳过
这不是 bug,是成本模型算出来的保守决策。
index_merge 不可靠,不能当解决方案依赖
即使两边都有单列索引(比如 INDEX(name) 和 INDEX(city)),MySQL 也未必启用 index_merge:
- MySQL 5.7 默认关闭
index_merge;8.0+ 虽默认开启,但union模式(对应OR)触发条件极苛刻 - 需数据量小、选择度高、无复合索引干扰,且优化器估算合并成本低于全表扫描
-
EXPLAIN中出现type=index_merge是小概率事件,线上环境基本不可控 - 就算启用了,也要做内存合并、去重、回表,I/O 和 CPU 开销仍高于单索引查询
联合索引对 OR 基本无效
有人以为建个 INDEX(name, city) 就能救场,实际不行:
- 联合索引依赖最左前缀,
WHERE name = ? OR city = ?无法构造连续扫描路径 -
city = ?在 B+ 树里是分散在不同name子树下的,物理上不连续,跳转成本高 - 优化器评估后认为:不如一次顺序扫描来得干脆
- 即便拆成
UNION ALL,第二个子句WHERE city = ?依然无法使用(name, city)联合索引——必须额外建INDEX(city)
用 UNION ALL 替代是目前最可控的解法
把“一个查询覆盖两棵树”改成“两个查询各走一棵树”,强制每个分支独立命中索引:
- 改写示例:
SELECT id, name FROM t WHERE status = 'paid' UNION ALL SELECT id, name FROM t WHERE created_at > '2025-01-01' AND status != 'paid' - 必须确保每个子句都能命中索引,否则只是把问题拆成两份
- 原查询带
LIMIT或ORDER BY?不能只在外层加,得分别在子查询里加,否则分页漏数据、排序错乱 - 若业务能确认两个条件天然不重叠(比如状态和时间有强约束),可删掉
AND status != 'paid',避免Using temporary
真正容易被忽略的是:UNION ALL 的每个子句,都得有自己对应的单列索引支撑——没有 INDEX(city),光靠 INDEX(name, city) 是救不了 WHERE city = ? 的。











