or条件本身不破坏索引,但优化器常因代价过高放弃索引,导致type:all、key:null;应优先用in替代同字段多值or,或用union all拆分并确保各子查询独立走索引。

OR 条件本身不“破坏”索引,但会让 MySQL 优化器放弃使用索引——这不是 bug,而是它基于代价模型做出的保守选择。
核心原因就一条:OR 要求合并多个不连续的扫描路径,而 B+ 树索引天然只支持单路径有序收敛(比如 AND 下不断缩小范围)。优化器算下来,随机跳转 + 合并结果的成本,大概率高于一次顺序全表扫描。
EXPLAIN 显示 type: ALL、key: NULL 就是明确信号
只要看到这两项,基本可以断定:这个 OR 没走任何索引。常见触发场景包括:
-
name = 'Alice'有索引,但city = 'Beijing'没索引 → 整个WHERE放弃索引 -
status = 'paid'和created_at > '2025-01-01'各自都有单列索引,但优化器没启用index_merge union→ 仍走全表 - 混用函数或隐式转换,例如
DATE(created_at) = '2025-01-01'或id = '123'(id是整型)→ 对应分支直接失效
别信“只要两边都有索引就能走”的说法
MySQL 的 index_merge union 是个“看心情”的功能:
- 默认在 5.7 关闭,8.0+ 虽开启,但只对高选择度、小数据量、无复合索引干扰的场景才可能触发
- 即使触发,执行计划里会出现
type: index_merge和Extra: Using union(...),但实际性能常不如预期——它要建临时表、去重、排序 - 联合索引
(a, b)对a = 1 OR b = 2完全无效,因为b = 2不满足最左前缀,无法定位起始点
UNION ALL 不是万能解药,但它是目前最可控的手段
把一个查询拆成多个独立子查询,等于告诉优化器:“每个都给我走索引”。但必须满足几个硬性条件:
- 每个子查询的
WHERE条件字段,必须有对应可用索引(name = ?需INDEX(name),city = ?需INDEX(city)) - 所有子查询返回的列数、顺序、类型、NULL 性必须完全一致,否则报错
ERROR 1222 - 原查询若有
LIMIT或ORDER BY,只能加在外层;子查询里加会导致分页错乱、结果缺失 - 如果业务能确认条件互斥(比如
status IN ('paid', 'refunded')),就用UNION ALL;否则得评估是否真需要去重,再决定包一层SELECT DISTINCT ...
OR a = ? OR b = ? 建了联合索引 (a, b),第二个分支依然走不了——它只对 a = ? AND b = ? 有效。想让 OR 稳定走索引,得按分支分别配单列索引,再用 UNION ALL 显式驱动。










