mysql中or常致type=all,因优化器难安全合并多索引路径;union all拆分需满足三前提:各子查询仅含一个等值索引条件、对应字段均有独立索引、select列数/顺序/类型完全一致。

为什么OR一出现,EXPLAIN就显示type=ALL
不是MySQL故意不走索引,而是优化器在多数情况下根本没法安全合并多个索引路径。比如 WHERE name = 'Alice' OR city = 'Beijing',即使 name 和 city 各有单列索引,B+树里这两个值散落在完全不相干的位置,强行“跳着查”再去重,开销可能比全表扫描还大。所以优化器直接放弃,选了最保守的 type: ALL。
只有极少数情况能走索引:MySQL 8.0+ 且所有 OR 分支都严格命中同一复合索引的最左前缀(如索引是 (a,b),条件是 (a=1 AND b=2) OR (a=1 AND b=3)),但这种写法太脆弱,线上几乎不可控。
UNION ALL 拆分必须满足的三个前提
拆了没用,甚至更慢,往往是因为漏掉了关键约束。真正生效的前提是:
- 每个子查询的 WHERE 条件里,只保留一个可走索引的字段等值判断(如
name = 'Alice'),不能混入其他非索引字段或函数 -
name和city字段各自要有独立索引;如果其中一个是无索引字段(如remark),那对应子查询仍是type: ALL - 所有子查询 SELECT 的列数、顺序、类型必须完全一致,否则报错
ERROR 1222;别名要对齐,比如都写SELECT id, name AS name
带分页或排序时,LIMIT不能只写在外层
原查询是 SELECT * FROM users WHERE status = 'paid' OR level > 10 ORDER BY created_at DESC LIMIT 20,如果只在外层加 LIMIT,UNION ALL 会先把两个子查询的全部结果拉出来再截断,数据量一大就崩。
正确做法是每个子查询自己估算上界并加 LIMIT(比如预估每边最多返回 50 行):
(SELECT * FROM users WHERE status = 'paid' ORDER BY created_at DESC LIMIT 50) UNION ALL (SELECT * FROM users WHERE level > 10 ORDER BY created_at DESC LIMIT 50) ORDER BY created_at DESC LIMIT 20
注意外层必须再套一次 ORDER BY,否则结果顺序不可控;UNION ALL 不保证顺序,也不去重。
什么时候不该硬拆UNION ALL
不是所有 OR 都适合拆。以下情况改了反而更差:
- 所有条件都在同一字段上:如
status = 'pending' OR status = 'processing'→ 直接改status IN ('pending', 'processing'),MySQL 对这个优化很成熟 - 已经存在覆盖索引:比如
SELECT id, status FROM orders WHERE status = 'shipped' OR order_time > '2026-01-01',而索引是(status, order_time, id),这时可能走index_merge或直接用索引下推 - 数据量极小(
真正容易被忽略的是:拆完之后没验证每个子查询是否真的走了索引。务必对每个括号内的 SELECT 单独跑一遍 EXPLAIN,看 key 和 rows 是否合理 —— 这一步跳过,等于白干。











