OR导致索引失效的根本原因是MySQL优化器判断使用索引反而更慢,只要任一条件无法走索引(如无索引、类型不匹配、函数包裹),整个WHERE就退化为全表扫描,EXPLAIN显示type=ALL、key=NULL。

OR条件导致索引失效的根本原因
MySQL优化器在遇到 OR 连接多个条件时,只要其中任一条件无法走索引(比如字段无索引、类型不匹配、函数包裹),整个 WHERE 子句就大概率退化为全表扫描——不是它“不想用”,而是它判断“用索引反而更慢”。EXPLAIN 中常表现为 type=ALL、key=NULL,哪怕其他条件列明明建了索引。
哪些OR写法会直接跳过索引
以下情况在实际存储过程中高频出现,且极易被忽略:
-
WHERE status = 'done' OR create_time > '2024-01-01':若status有索引但create_time没索引,整条语句放弃索引 -
WHERE id = 123 OR phone LIKE '%138%':LIKE左模糊 + 无索引字段,触发全表扫描 -
WHERE user_id = 1001 OR SUBSTR(name, 1, 3) = 'Tom':对索引列name做函数处理,该分支索引失效,连带整个OR结构失效 -
WHERE a = 1 OR b = 'x',但复合索引是INDEX idx_a_c (a, c):b不在索引中,且a单独出现时虽可走索引,但优化器仍倾向全表扫描以避免回表+合并开销
替代方案:UNION ALL 比 OR 更可控
把 OR 拆成独立查询再合并,能让每个分支单独走索引。关键点在于:
- 用
UNION ALL代替UNION,除非你明确需要去重——UNION隐含DISTINCT排序,额外引入Using temporary; Using filesort - 确保每个子查询的
WHERE条件都能命中对应索引,例如:SELECT * FROM t WHERE status = 'shipped'和SELECT * FROM t WHERE amount > 1000各自有单列索引或覆盖索引 - 如果两个子查询结果集结构不同(字段数/类型不一致),需显式对齐,否则存储过程编译失败
示例片段(在存储过程中):
SELECT id, name FROM orders WHERE status = 'paid' UNION ALL SELECT id, name FROM orders WHERE amount > 5000;
容易被忽略的边界细节
即使改用 UNION ALL,仍有几个硬伤常被绕过:
- 两个子查询返回字段顺序、别名、数据类型必须严格一致,否则存储过程调用时报
ERROR 1222 (21000): The used SELECT statements have a different number of columns - 若原
OR查询带ORDER BY或LIMIT,不能简单加在外层——得在每个子查询里分别加,再外层统一排序分页,否则逻辑错乱 - 当某分支查出上万行、另一分支只查出几行时,
UNION ALL的执行时间≈慢分支耗时,但至少避免了全表扫描的不可控抖动
真正卡住性能的,往往不是“有没有索引”,而是“有没有让每个条件都独立、干净地触达索引”。OR 是语法糖,不是执行路径的保障。

















