mysql中or条件任一侧无索引即全表扫描;解决方法为给缺失列建索引或改写为union/union all,后者需注意字段一致、去重开销及语义等价。

OR条件中部分列没索引,直接全表扫描
只要 OR 左右任一侧的字段没索引,MySQL 就会放弃走索引——不是“部分用”,而是整个 OR 条件都不走索引,直接 type: ALL。这是因为优化器判断:一边走索引查一部分,另一边全表扫再合并,不如一次全表扫来得干脆。
常见错误现象:
-
EXPLAIN中key为NULL,type是ALL - 明明
name有索引,WHERE name = 'a' OR age = 25却不走索引(因为age没索引)
解决办法只有两个硬手段:
- 给
OR右侧缺失索引的列单独建索引(如CREATE INDEX idx_age ON student(age)) - 或者改写成
UNION,让两边各自走自己的索引
UNION 改写要注意字段顺序和去重开销
UNION 能强制让每个子查询独立使用索引,但它默认去重,会额外排序 + 去重,影响性能;如果业务能接受重复数据,优先用 UNION ALL。
示例对比:
-- ❌ 原始写法(可能全表扫) SELECT * FROM student WHERE name = 'Alice' OR age = 20; <p>-- ✅ UNION ALL 改写(两个子查询各自走索引) SELECT <em> FROM student WHERE name = 'Alice' UNION ALL SELECT </em> FROM student WHERE age = 20 AND name != 'Alice';</p>
注意点:
- 第二个子查询加了
AND name != 'Alice'是为了语义等价(避免重复),但会削弱age索引的过滤效果 - 如果表里
name和age值天然不重叠(比如联合唯一约束),可省略去重逻辑 - 字段顺序、类型、NULL 处理必须完全一致,否则
UNION报错
Index Merge 是自动机制,但不可控也不稳定
当 OR 两侧字段都有索引时,MySQL 可能启用 index_merge(执行计划中 type: index_merge,key 显示多个索引名)。但这不是总发生——它依赖数据分布、索引选择度、优化器成本估算。
典型不稳定表现:
- 数据量小或选择度差(比如
gender只有 'M'/'F')时,优化器倾向全表扫描 - 复合索引和单列索引共存时,可能只选一个,忽略
index_merge - MySQL 5.6+ 才较完善支持,低版本基本不触发
所以别指望它,更别在代码里隐式依赖它。想稳,就手动建索引或改 UNION。
真正容易被忽略的边界情况
很多人以为“两边都有索引就万事大吉”,但还有几个隐藏坑:
-
OR套在子查询或函数里,比如WHERE id IN (SELECT a FROM t1) OR status = 'done',外层OR依然可能失效 - 联合索引只覆盖
OR中的一个条件,另一个条件即使有单列索引,也可能因成本估算被跳过 -
OR和ORDER BY/LIMIT共存时,index_merge很难生效,容易退化成临时表 + filesort
最保险的做法:对高频 OR 查询,提前用 EXPLAIN 验证执行计划,别信“应该会走”。











