mysql 5.7 中 or 条件基本不走索引,根本原因是 innodb 缺乏安全可控的索引合并能力,且 b+ 树天然适合 and 而非 or;union all 是更可靠的替代方案,但需注意字段一致性、外层排序及子查询索引验证。

MySQL 5.7 中绝大多数 OR 条件会触发全表扫描,不是优化器“不努力”,而是它在 InnoDB 引擎下**缺乏安全、可控的索引合并能力**——尤其当条件跨列、含范围或隐式转换时,type = ALL 几乎是唯一合理选择。
为什么 OR 在 InnoDB 5.7 下基本不走索引
根本原因在于 B+ 树索引天然适合 AND 的收敛路径,而 OR 要求合并多个独立扫描结果。InnoDB 5.7 默认禁用 index_merge,即使手动开启 optimizer_switch='index_merge=on',也只在极苛刻条件下生效:
- 每个
OR子句必须命中**独立的单列索引**(如WHERE a = 1 OR b = 2,需有INDEX(a)和INDEX(b)) - 不能含函数、类型转换(如
phone = 138对比phone = '138')、IS NULL或混合范围条件(如a > 10 OR b = 'x') - 统计信息必须足够准确,否则优化器宁可选全表扫描也不冒险合并
UNION ALL 是更可靠的替代方案
它把逻辑拆成多个可预测的子查询,每个都能独立走索引,避免了优化器对 OR 路径的代价误判:
- 每个
SELECT必须返回**相同数量、顺序、类型**的字段,否则报错ERROR 1222 - 用
UNION ALL而非UNION:去重和排序开销大,且多数OR场景本就互斥(如status = 'A' OR status = 'B') -
ORDER BY和LIMIT只能加在外层,不能放在每个子句里 - 务必单独验证每个子查询的
EXPLAIN输出,确认type是ref或const,而非ALL
哪些 OR 场景连 UNION ALL 都难救
不是所有 OR 都适合硬拆,以下情况需换思路:
- 条件存在逻辑重叠(如
id > 100 OR created_at > '2023-01-01'),UNION ALL会重复返回同一行,UNION又慢 - 右侧是子查询(如
col IN (SELECT ...)),改写后可能引发多次执行,反而更慢 - 单列多值等价于
IN(如status = 'A' OR status = 'B'),优先尝试status IN ('A','B')—— 它在 5.7 中通常能走索引,但前提是该列有索引且无隐式转换 - 模糊查询带前导 %(如
name LIKE '%abc'),索引本身已失效,OR只是雪上加霜;此时应考虑REVERSE(name)+ 函数索引,或引入 Elasticsearch
真正容易被忽略的是:哪怕你建了所有单列索引,只要有一个 OR 子句触发隐式转换(比如字符串字段传入数字值),整个合并逻辑就直接崩掉——优化器连尝试 index_merge 的机会都不会给。










