mysql 5.7 中 or 条件通常不走索引,因 innodb 默认禁用 index_merge 且合并条件苛刻;推荐用 union all 替代,确保各子查询独立走索引,或检查单列索引是否完备。

MySQL 5.7 中 OR 条件不走索引,不是“优化器偷懒”,而是它根本没法安全、高效地合并多个索引扫描路径——尤其在 InnoDB 引擎下,绝大多数 OR 查询会退化为全表扫描。
OR 条件在 InnoDB 下基本不走索引
MyISAM 引擎支持对 OR 的索引合并(index merge),但 InnoDB 在 5.7 版本中默认禁用该策略,且即使开启 optimizer_switch='index_merge=on',也仅在极少数严格条件下生效:所有 OR 子句必须各自命中**独立的、有效的单列索引**,且不能含函数、类型转换或范围条件混用。
常见误判点:
-
WHERE name = 'alice' OR city = 'beijing':若只有name有索引、city没索引 → 必走全表扫描 -
WHERE id = 1 OR age > 30:即使id是主键、age有索引,InnoDB 5.7 通常也不合并 → type = ALL -
WHERE status = 'A' OR status = 'B':看似简单,但等价于status IN ('A','B'),仍可能因选择率高或统计信息不准被弃用索引
为什么 UNION ALL 是更可靠的选择
用 UNION ALL 替代 OR 不是“绕路”,而是把逻辑拆成多个可索引的子查询,再合并结果——每条子查询都能独立走索引,优化器也更容易评估代价。
实操要点:
- 确保每个子查询的 WHERE 条件都落在**已建索引的列上**,且无隐式转换(如
phone = '138'而非phone = 138) - 用
UNION ALL而非UNION,避免去重开销;业务确认无重复数据时,这是安全且更快的 - 示例:
SELECT * FROM users WHERE name = 'alice' UNION ALL SELECT * FROM users WHERE city = 'chicago'→ 两个子查询分别走idx_name和idx_city - 注意:若两个子查询返回字段顺序/类型不一致,会报错,需显式对齐(如都写
SELECT id, name, city)
IN 替换 OR 的边界很窄
IN 看似简洁,但在 5.7 中它只是语法糖,底层仍可能触发全表扫描,尤其当值列表过长、或字段无索引、或存在隐式转换时。
哪些情况 IN 依然失效:
-
WHERE phone IN (13800138000, 13900139000)→phone是VARCHAR,数字字面量触发隐式转换 → 索引失效 -
WHERE created_at IN ('2024-01-01', '2024-01-02')→ 单列索引有效,但若换成DATE(created_at)就立刻失效 -
WHERE status IN ('pending', 'processing', 'failed')→ 若status基数低(如只有 3 个值)、表又很大,优化器可能认为全表扫描比索引回表更便宜
真正起效的前提:索引必须“够用”
别只盯着 OR 或 IN,先确认索引本身是否匹配查询模式。很多问题表面是 OR 不走索引,根子在索引设计缺陷。
检查清单:
- 联合索引
(a, b)对WHERE a = ? OR b = ?无效——OR 无法利用最左前缀,也不能跨列跳用 - 想支持
WHERE name = ? OR city = ?,就得建两个单列索引:KEY idx_name (name)和KEY idx_city (city) - 如果高频查
name和city组合,优先考虑联合索引(name, city)+ 单独idx_city,而非硬扛 OR - 执行
EXPLAIN FORMAT=JSON看key是否非 NULL、used_key_parts是否完整命中、rows是否远小于表总行数
最易被忽略的一点:即使你建了两个单列索引,InnoDB 5.7 默认也不会启用 index merge,得手动打开开关并验证效果——线上环境别盲目开,先压测。











