UNION子查询慢主因是嵌套层级导致优化器失效,无法下推谓词、复用索引或准确估算行数,被迫物化+排序去重;应优先转JOIN或改UNION ALL,90%场景无需去重。

UNION 子查询慢,往往不是 UNION 本身的问题,而是它被用在了错误的位置、错误的嵌套层级,或者掩盖了更底层的执行缺陷。直接加索引或改写语法,常治标不治本。
UNION 在子查询里为什么比 JOIN 还慢?
当 UNION 出现在子查询中(比如 WHERE x IN (SELECT ... UNION SELECT ...) 或 FROM (SELECT ... UNION SELECT ...) AS t),数据库通常无法有效下推谓词、无法复用索引、甚至被迫物化整个中间结果集。
-
UNION隐含去重逻辑,即使你确定没重复,数据库仍要走DISTINCT流程(排序 + 去重 or hash 去重),额外消耗 CPU 和内存 - 子查询中的
UNION往往导致执行计划里出现Using temporary和Using filesort,尤其在 MySQL 中非常伤性能 - 优化器对嵌套
UNION的估算极不准确,rows常严重低估,导致选错连接顺序或访问方式 - 如果两个
SELECT分支涉及不同表、不同条件,且没有共用驱动表,UNION就成了“强行合并两套独立执行路径”,丧失并行/复用机会
哪些场景下 UNON 实际可以保留?
不是所有 UNION 都该删——关键看它是否承担了语义上不可替代的“并集”职责,且能被数据库高效执行。
- 两个查询完全独立、无交集、目标字段结构一致,且都带强过滤条件(例如按时间分区查昨日+今日订单)→ 可保留,但建议改用
UNION ALL - 查询来源是不同物理表(如
order_2025和order_2026),且没有统一视图 →UNION ALL是合理选择 - 外层有
LIMIT,但数据库支持UNION下推(如 PostgreSQL 14+),此时可接受;MySQL 8.0 对UNION+LIMIT仍常全量合并再截断
UNION 子查询重构的三个实操方向
优先级从高到低:先看能否转为 JOIN,再看能否拆成应用层多次查询,最后才考虑 UNION ALL + 索引强化。
- 把
WHERE col IN (SELECT a FROM t1 UNION SELECT b FROM t2)改成EXISTS或LEFT JOIN:前者避免物化,后者可利用驱动表索引 - 若两个子查询共用同一张主表(如都查
user_id),提取主表为驱动,用OR或CASE WHEN合并条件,比UNION更易走索引 - 确认是否真需要去重:90% 的业务场景中,
UNION ALL就够用;只要把UNION换成UNION ALL,就能跳过排序去重步骤,速度常提升 3–10 倍
最容易被忽略的坑:统计信息 & 参数嗅探
哪怕你把 UNION 改成了 JOIN,如果执行计划回归了(比如统计信息更新后优化器突然放弃走索引),性能照样崩。尤其在 SQL Server 或 Oracle 中,参数嗅探会让同一个语句在不同参数值下走完全不同路径。
验证方式很简单:EXPLAIN(MySQL/PostgreSQL)或 SET STATISTICS XML ON(SQL Server)跑两次,传入不同参数值,对比 key、type、rows 是否一致。不一致,就不是 SQL 写法问题,而是执行计划稳定性问题——这时候该上 OPTION (RECOMPILE) 或更新统计信息,而不是继续调 SQL。

















