mysql中or条件常致索引失效,因优化器难合并多索引路径;优先用union all拆分各分支并确保字段均有独立索引,配合explain验证执行计划。

OR 条件在 MySQL 中极易导致索引失效,不是写法错了,是优化器根本没法高效走索引 —— 除非所有 OR 分支都严格命中同一复合索引的最左前缀,且无函数、无类型转换、无 NULL 混合。真实场景里这条件太苛刻,所以别赌运气,直接用 UNION ALL 拆。
为什么 OR 常常不走索引?
MySQL 优化器对 OR 的处理逻辑本质是“难以合并多个索引扫描路径”。哪怕 a = 1 OR b = 2 中 a 和 b 各有独立索引,优化器也大概率放弃索引、全表扫描(type: ALL)。只有在 MySQL 8.0+ 且满足严格条件时,才可能触发 index_merge;但该策略仍需回表、去重、排序开销,性能不稳定。
常见错误现象:
-
EXPLAIN显示key为空、type为ALL或range但rows接近总行数 - 查询耗时随数据量非线性增长(比如从 10 万到 100 万行,耗时翻 5 倍以上)
- 加了索引也没用,
OR一出现,索引就“隐身”
UNION ALL 替代 OR 的实操要点
把一个含 OR 的查询,拆成多个子查询,各自走自己的索引,再用 UNION ALL 合并结果 —— 这是最可控、效果最稳的方案。
- 必须用
UNION ALL,不是UNION:后者会强制去重 + 排序,额外消耗 CPU 和临时磁盘,尤其大数据量时明显拖慢 - 每个子查询要能独立走索引:确保
WHERE子句中只保留该分支对应字段的等值条件(如name = 'Alice'),不要混入其他过滤逻辑 - 若原查询有
ORDER BY ... LIMIT,不能只在外层加:应先在每个子查询里加LIMIT(按需估算上界),再外层统一排序分页,否则可能漏数据 - 字段顺序和类型必须完全一致:列数、名称(或用别名对齐)、数据类型要兼容,否则报错
UNION types not compatible
示例:
SELECT * FROM users WHERE name = 'Alice' OR city = 'Beijing'; → 改为: (SELECT * FROM users WHERE name = 'Alice') UNION ALL (SELECT * FROM users WHERE city = 'Beijing');
IN 和 OR 的边界在哪?什么时候不该换 UNION ALL?
当 OR 全部作用于**同一字段的多个等值判断**时,IN 是更简洁、更安全的选择,MySQL 对其索引优化成熟稳定。
- 适用:
status = 1 OR status = 2 OR status = 3→ 直接改status IN (1, 2, 3),仍走索引 - 不适用:
name = 'Alice' OR email = 'alice@example.com'→ 两个字段,必须拆UNION ALL - 注意
IN对NULL的特殊行为:col IN (1, NULL)永远不匹配NULL行,而col = 1 OR col IS NULL会匹配 —— 语义不同,不能无脑替换
容易被忽略的验证动作
改完不是就结束了。真正关键的是用 EXPLAIN 对比执行计划:
- 原
OR查询:确认是否出现type: ALL或key: NULL - 新
UNION ALL查询:分别对每个子查询EXPLAIN,看key是否命中预期索引、rows是否显著下降 - 外层包装后整体
EXPLAIN:关注是否出现Using temporary或Using filesort—— 如果有,说明ORDER BY/LIMIT位置不合理,得调整子查询内部的排序和截断
索引本身也要配合:如果拆出来的字段没索引,UNION ALL 也白搭。先确保 name 有 idx_name,city 有 idx_city,再动手改 SQL。











