union all替代or是最可控的优化手段,前提是各分支能独立走索引且数据交集明确;or常不走索引因mysql难以合并跨字段索引,尤其存在函数、隐式转换或is null时,优化器直接回退全表扫描。

直接用 UNION ALL 替代 OR 是最可控、见效最快的优化手段,但前提是每个分支能独立走索引,且你清楚数据交集情况。
为什么 OR 常常不走索引
MySQL 5.7 及更早版本几乎从不为跨字段的 OR(如 status = 'paid' OR city = 'Shanghai')启用多个单列索引;即使 8.0 支持 Index Merge,也要求所有条件都满足:无函数、无隐式转换、无 IS NULL、各条件必须严格命中独立可用索引。现实中只要有一条不满足,优化器就直接放弃索引,回退到 type: ALL。
-
EXPLAIN显示key为NULL或type是ALL,哪怕单个条件单独执行很快,这就是典型信号 -
OR和LIKE '%xxx'、col IS NULL、DATE(create_time) = '2026-01-01'搭配,基本等于主动放弃索引 - 复合索引只在所有
OR分支都落在其最左前缀上时才可能生效(例如(a,b)索引 +(a=1 AND b=2) OR (a=1 AND b=3)),其他情况别指望
UNION ALL 替代 OR 的硬性前提
不是所有 OR 都适合拆,拆错反而更慢。必须同时满足:
- 每个子查询的
WHERE条件能独立命中某个索引(例如status有索引、city也有索引)——先用EXPLAIN SELECT * FROM t WHERE status = 'paid'单独验证 - 分支数量建议 ≤ 5~10 个;超过后 MySQL 可能拒绝为每个分支生成索引访问路径,执行计划变复杂
- 所有子查询的字段数、顺序、类型、是否允许
NULL必须完全一致,否则报错ERROR 1222 (21000): The used SELECT statements have a different number of columns - 如果原查询有
GROUP BY或HAVING,不能简单拆;需在每个子查询内先聚合,再在外层二次聚合(代价高,慎用)
怎么写才真正快:细节决定成败
语法等价不等于性能等价。以下这些点不注意,UNION ALL 就只是把问题从一个地方挪到另一个地方:
- 必须用
UNION ALL,不是UNION;后者会触发临时表 + 排序去重,大数据量下开销远超OR - 若业务允许少量重复(比如主键/唯一键天然保证无交集),第二条及之后的子查询里不用加
AND status != 'paid'这类“去重补丁”——加了反而限制索引选择,还可能引入空值逻辑错误 - 原始查询若有
ORDER BY ... LIMIT N,不能只在外层加;必须在每个子查询里各自加LIMIT N,再合并后统一排序,否则可能漏掉本该排前 N 的结果 - 时间范围等公共条件(如
date BETWEEN '2025-01-01' AND '2025-12-31')要复制进每个子查询,不能只写在外层
比 UNION ALL 更轻量的替代:IN 什么时候能用
当 OR 全是同一字段的等值判断(如 id = 1 OR id = 2 OR id = 3),优先改用 IN (1, 2, 3):
-
IN在 MySQL 中优化成熟,仍能走索引,写法简洁,维护成本低 - 值数量控制在几百以内;超过后解析开销上升,某些版本会触发文件排序
- 避免
IN (SELECT ...),这种子查询性能通常比OR更差,应考虑JOIN -
IN对NULL不敏感:col IN (1, NULL)永远不匹配NULL行,和OR行为不一致,业务逻辑需确认
真正难处理的是那些既不适合 IN、又无法靠复合索引覆盖的 OR ——比如涉及不同字段、带函数、或混用范围条件。这时候 UNION ALL 不是银弹,而是你亲手接管执行计划的开关。它快,是因为你明确告诉 MySQL “怎么查”,而不是让它猜;但它也要求你对每个分支的索引行为、数据分布、交集比例有真实判断,而不是机械拆分。











