MySQL中OR条件常导致索引失效,因优化器权衡后选择全表扫描而非低效的索引合并;推荐用UNION ALL替代OR,并确保各子查询能独立走索引,最终效果需以EXPLAIN验证。

MySQL里OR条件一用,索引就失效,不是bug,是优化器的理性选择——它宁可全表扫描,也不愿在部分走索引、部分没法走的情况下硬凑执行计划。
为什么OR会让索引“突然不工作”
MySQL对OR的处理很保守:只要OR两边任意一个字段没索引,或者索引类型/数据类型不匹配,优化器大概率放弃所有索引,直接type: ALL。这不是偷懒,而是因为合并多个索引路径(比如Index Merge Union)的成本可能高于扫全表,尤其当表不大、或非索引列匹配行数很多时。
常见失效现象:EXPLAIN里key为NULL、type是ALL、Extra里没有Using union;哪怕id = 1 OR name = 'xxx'中id是主键,name没索引,照样全表扫。
- 复合索引对
OR基本无效——(a,b)索引无法加速a = 1 OR b = 2 -
OR和IN不同,优化器不会把OR自动转成IN来用索引 - 即使两个字段都有单列索引,
Index Merge也未必触发,取决于统计信息和行数估算
UNION ALL替代OR是最稳的写法
把一个OR查询拆成多个独立子查询,每个都能走自己的索引,再用UNION ALL合并,几乎总比原OR快。关键点不在语法,而在执行路径的确定性。
例如:SELECT * FROM users WHERE name = 'Alice' OR city = 'Beijing' →
SELECT * FROM users WHERE name = 'Alice' UNION ALL SELECT * FROM users WHERE city = 'Beijing' AND name != 'Alice';
注意加AND name != 'Alice'这类排除重复的条件,否则UNION ALL会返回重复行;如果业务能保证name和city组合唯一,这步可省。
- 用
UNION ALL而非UNION,少一次去重排序开销 - 每个子查询必须能独立命中索引——检查
EXPLAIN确认key非空 - 如果原查询有
ORDER BY或LIMIT,得在外层再套一层,不能直接加在子句里
什么时候该建索引,而不是改SQL
如果OR条件里的字段确实高频查询,且数据量大,优先补索引,而不是每次写UNION。但别乱建:
- 给
OR中每一个字段单独建索引,MySQL才可能启用Index Merge Union;查EXPLAIN输出是否有type: index_merge和Extra: Using union(...) - 不要为
OR建复合索引,比如(a,b)对a = 1 OR b = 2没用;但若查询固定是a = 1 OR (a = 2 AND b = 3),可以考虑(a,b)覆盖后半段 - 低选择性字段(如
status只有3个值)单独建索引意义不大,不如和高选择性字段组合
EXPLAIN是唯一裁判,别猜
所有优化都得靠EXPLAIN验证,不是“加了索引就一定走”。同一句SQL,在不同数据量、不同MySQL版本、甚至ANALYZE TABLE前后,执行计划都可能变。
重点盯三处:
-
type:从ALL变成ref或range才算有效 -
key:显示实际使用的索引名,NULL就是没走 -
rows:预估扫描行数,下降明显才说明优化生效
真正容易被忽略的是:OR查询的性能拐点往往藏在数据分布里——比如city = 'Beijing'匹配10行,city = 'Lhasa'匹配10万行,优化器对后者的判断会完全不同。没跑过真实数据量,EXPLAIN只是参考。


















