OR常致全表扫描,因优化器放弃索引;提速关键在于各分支独立走索引;UNION ALL可替代但需满足索引、列一致等硬性条件,否则更慢。

OR 条件在 MySQL 中极易触发全表扫描,不是写法“错”,而是优化器主动放弃索引——只要任一分支无法走索引,整个 WHERE 就大概率退化为 type=ALL。能否提速,取决于你能不能让每个条件分支各自独立命中索引。
为什么 OR 常常不走索引?
B+ 树索引适合单点或连续范围查找,而 OR 的多个分支可能落在索引中完全不相干的位置(比如 status = 'paid' 和 level > 10 分别对应两段离散叶子节点),合并成本高,优化器直接判定:扫全表反而更快。
典型失效场景包括:
-
WHERE a = 1 OR b = 2(a、b 各有单列索引,但无联合索引) -
WHERE name = 'x' OR remark LIKE '%y%'(后者必然无法走索引,拖垮整体) -
WHERE id = 1 OR created_at > '2024-01-01'(等值 + 范围,类型/结构不一致)
注意:WHERE id = 1 OR id = 2 OR id = 3 这种同字段多值 OR,MySQL 会自动转成 IN 并走索引,不用改。
用 UNION ALL 替代 OR 的硬性前提
这不是语法替换,而是执行路径重构。必须满足以下全部条件,否则改了更慢:
- 每个
OR分支对应的字段,**各自有可用索引**(用EXPLAIN单独验证SELECT * FROM t WHERE status = 'shipped'和SELECT * FROM t WHERE customer_id = 123都能走key) - 所有子查询返回的**列数、顺序、类型、NULL 属性必须完全一致**,否则报错
ERROR 1222 (21000): The used SELECT statements have a different number of columns - 必须用
UNION ALL,不是UNION;去重逻辑由业务控制(比如加AND status != 'paid'排除重复),否则UNION会触发Using temporary和排序,开销翻倍
带 ORDER BY / LIMIT 的正确写法
UNION 整体的 LIMIT 不会下推到各分支,直接套在外层会导致漏数据。例如要取 top 10:
SELECT id, name FROM users WHERE status = 'paid' UNION ALL SELECT id, name FROM users WHERE level > 10 AND status != 'paid' ORDER BY id DESC LIMIT 10;
这实际是先合并全部结果再排序取 10,性能差。正确做法是:
- 每个子查询单独加
LIMIT(如预估每支最多出 10 行,就写LIMIT 10) - 外层再
ORDER BY ... LIMIT 10二次筛选 - 若原始查询有
GROUP BY或HAVING,不能直接拆——需子查询内先聚合,外层再汇总,代价更高,慎用
什么情况下 UNION ALL 也救不了?
别盲目拆。以下情况改了白改,甚至更慢:
- 所有
OR条件都落在同一个复合索引的最左前缀上(如WHERE (a = 1 AND b = 2) OR (a = 1 AND b = 3),本身就能走idx_a_b) - 某个分支字段根本没索引(比如
remark LIKE '%abc%'),拆出来仍是全表扫描 - 表很小(
- 结果集天然大量重叠,又强行用
UNION ALL,导致最终结果膨胀,网络和内存压力反增
真正关键的不是“用了 UNION”,而是每个子查询是否真的只扫索引定位的那几行——否则只是把全表扫描拆成好几次而已。


















