UNION 比 UNION ALL 慢的根本原因是其自动去重,需执行隐式 DISTINCT,涉及排序、哈希或临时表扫描,带来额外 I/O 和 CPU 开销;实测耗时通常是 UNION ALL 的 2.3–4.1 倍。

UNION 为什么会比 UNION ALL 慢
根本原因在于 UNION 会自动去重,而 UNION ALL 完全跳过这一步。去重操作实际等价于对全部结果集执行一次隐式 DISTINCT,这意味着数据库要:排序、哈希或临时表扫描——无论哪种策略,都带来额外 I/O 和 CPU 开销。
实测中,当两个子查询各返回 10 万行、且重复率低于 5% 时,UNION 耗时通常是 UNION ALL 的 2.3–4.1 倍(取决于引擎和索引情况);若重复率接近 0%,差距只会更大,因为去重纯属白做工。
什么时候必须用 UNION 而不能换 UNION ALL
只有当你明确需要逻辑上“合并后无重复行”时才必须用 UNION。常见真实场景包括:
- 合并来自不同业务模块的用户 ID 列表,且最终要用于
IN子句——重复 ID 可能导致计划器误判或语义错误 - 报表口径要求“唯一客户数”,而数据源天然存在跨渠道重叠(如 App + Web 同一用户登录)
- CTE 中后续要对结果做
GROUP BY或窗口函数,但上游已含冗余,又不想在最外层再套一层DISTINCT
如何判断能否安全替换成 UNION ALL
关键不是看 SQL 写法,而是看数据语义是否天然互斥。检查方式很直接:
- 确认两个子查询的
SELECT列完全一致(类型、顺序、NULL 性),否则UNION ALL也会报错 - 分析业务逻辑:比如
SELECT id FROM orders WHERE status = 'paid'和SELECT id FROM orders WHERE status = 'shipped'—— 同一笔订单不可能同时是 paid 又 shipped(状态机约束),这时UNION ALL安全 - 用
(SELECT COUNT(*) FROM (... UNION) t) = (SELECT COUNT(*) FROM (... UNION ALL) t)快速验证实际重复行数为 0
MySQL / PostgreSQL / SQL Server 的行为差异点
虽然标准一致,但优化器处理方式不同,影响实测结果:
- MySQL 8.0+ 对
UNION默认使用临时表 + 文件排序,即使结果集很小;加ORDER BY会强制触发,不加也可能触发(取决于tmp_table_size) - PostgreSQL 在小结果集下倾向用哈希去重,内存足够时比 MySQL 快;但若超出
work_mem,会落盘,性能断崖下跌 - SQL Server 的
UNION执行计划里一定包含Sort或Hash Match (Aggregate)算子,而UNION ALL对应算子是纯流式合并,没有中间物化
所以同样 SQL,在三者上跑出的耗时比可能分别是 3x、2.1x、5.7x——别只信某一个数据库的测试结论。











