SQL 中 UNION 与 UNION ALL 都用于合并多个 SELECT 的结果集,核心区别在于是否去重:UNION 自动剔除重复行(等价于先 UNION ALL 再做 DISTINCT),UNION ALL 原样保留所有行。因为去重需要额外的排序或哈希开销,UNION 通常比 UNION ALL 慢得多——据 sqlqueries.in 的实测,合并两年产品名时 UNION 约 5.2 秒、UNION ALL 仅 2.1 秒。经验法则是:除非明确需要去重,否则默认用 UNION ALL。据 dbsyntax 的方言对照,二者均为 ANSI 标准,在 PostgreSQL、MySQL、SQL Server、Oracle、SQLite 中行为一致。
一、核心区别:一行之差在去重
UNION 像一次隐式的 DISTINCT,把合并后的结果集按整行去重;UNION ALL 只是把各段结果首尾拼接。用 SQLProStudio 的经典例子说明:
-- 表 A: 1, 2, 3 表 B: 2, 3, 4
SELECT id FROM table_a UNION SELECT id FROM table_b;
-- 结果: 1, 2, 3, 4 (重复行被去除)
SELECT id FROM table_a UNION ALL SELECT id FROM table_b;
-- 结果: 1, 2, 3, 2, 3, 4 (所有行保留)
腾讯 ima 知识库同样将其概括为”UNION 自动去除重复记录,UNION ALL 保留所有记录不做去重处理”。这个差异直接决定了二者在性能与语义上的取舍。
二、性能差距来自去重代价
UNION 必须为整份合并结果做排序或哈希以识别重复,数据量越大代价越高;UNION ALL 仅做追加,不做任何额外处理。下表汇总关键差异(综合 sqlqueries.in、OneUptime、dbsyntax):
| 对比维度 | UNION | UNION ALL |
|---|---|---|
| 重复行 | 去除 | 保留 |
| 性能 | 较慢(需排序/哈希去重) | 较快(直接拼接) |
| 是否隐式排序 | 是(去重过程可能重排) | 否(多数引擎保左后右顺序) |
| 结果正确性 | 去重后更安全 | 行可能被意外折叠 |
| 适用默认 | 否 | 是(分析场景首选) |
OneUptime 在 MySQL 视角下指出,UNION 的去重使引擎必然创建临时表,而 UNION ALL 在某些情形下可避免临时表。当结果集达十亿行量级时,dbsyntax 形容二者差距是”分钟级对比秒级”。
三、合并的硬性规则
无论用哪种,两段 SELECT 必须满足:
- 列数相同:每段返回的列数量一致,否则直接报错。
- 类型兼容:对应列的数据类型需可隐式转换(如
INT与BIGINT可合并,多数引擎会向更宽类型转换)。 - 列名取首段:结果集的列名来自第一个
SELECT,后续段的列名被忽略。
-- 错误:列数不一致
SELECT id, name FROM customers UNION ALL SELECT id FROM suppliers;
-- 正确:列数、顺序对应
SELECT id, name FROM customers UNION ALL SELECT order_id, customer_name FROM orders;
四、什么时候用 UNION
只有当重复行会导致结果错误时,才值得付出去重代价。典型场景:
求唯一值集合。例如跨”客户表”和”员工表”取所有不重复的邮箱:
SELECT email FROM customers UNION SELECT email FROM staff;
跨多份目录取唯一分类。把 2024 与 2025 两个商品目录的分类合并去重,得到完整分类清单。sqlqueries.in 把”生成唯一客户列表”列为 UNION 的标准用例。
五、什么时候用 UNION ALL
绝大多数合并其实不需要去重,此时 UNION ALL 既快又准确:
合并互不重叠的分区。按年/季度分表存储时,各分区数据天然不重叠:
SELECT revenue FROM sales_q1
UNION ALL SELECT revenue FROM sales_q2
UNION ALL SELECT revenue FROM sales_q3
UNION ALL SELECT revenue FROM sales_q4;
OneUptime 强调,若此处误用 UNION,当不同季度出现相同金额时会被错误折叠,导致汇总总额偏低。
日志与全量分析。日志表天然允许重复,分析时只需全量行,UNION ALL 避免无谓去重。
聚合前合并。先把多源数据 UNION ALL 成一个集合,再统一 GROUP BY,写法清晰且高效。
六、ORDER BY 与多路合并写法
ORDER BY 只能放在整段 UNION 的最后,作用于合并后的总结果;不能在单个 SELECT 内直接排序(除非包成子查询)。列名需引用第一段:
SELECT id, name, 'customer' AS type FROM customers
UNION ALL
SELECT id, name, 'supplier' AS type FROM suppliers
ORDER BY name;
多路合并可链式叠加任意数量的分支;若要对每段先取 Top N 再合并,需把每段包进子查询并各自加 ORDER BY ... LIMIT。
七、方言差异需注意
二者均为 ANSI 标准,在 PostgreSQL、MySQL、SQL Server、Oracle、SQLite、Snowflake、DuckDB、Redshift 上行为一致。唯一显著的例外是 BigQuery:它要求显式写出 UNION ALL 或 UNION DISTINCT,裸写 UNION 会报语法错误(dbsyntax 与 OneUptime 均提及)。此外 INTERSECT(两集交集)与 EXCEPT/MINUS(差集)与它们同属集合运算家族,Oracle 用 MINUS 表达差集,MySQL 8.0.31+ 才支持 INTERSECT/EXCEPT。
常见问题(FAQ)
UNION ALL 一定比 UNION 快吗?
在任何非极小的数据集上都更快,因为它跳过去重所需的排序或哈希;UNION 等价于 UNION ALL 后加 DISTINCT。
两段查询列数不同能合并吗?
不能,必须列数相同且对应列类型兼容,否则引擎直接报错。
结果列名以哪个为准?
以第一个 SELECT 的列名为准,后续段的列名被忽略,但列数和顺序必须对齐。
本文更新于 2026 年 8 月,内容综合SQLProStudio、sqlqueries.in、OneUptime、dbsyntax 等公开技术资料整理。