MySQL 用 UNION 和 UNION ALL 的区别
MySQL UNION 与 UNION ALL 的区别
UNION 和 UNION ALL 都是 MySQL 中用于合并两个或多个 SELECT 语句结果集的操作符。它们的核心区别在于:UNION 会自动去除重复行,而 UNION ALL 会保留所有行(包括重复行)。 这个差异在查询效率、数据完整性和使用场景上会产生显著影响。
一、基础语法与快速对比
两个操作符的语法几乎相同:
SELECT column1, column2 FROM table1
UNION [ALL]
SELECT column1, column2 FROM table2;
- 列数必须相同:所有 SELECT 语句选取的列数量必须一致。
- 数据类型兼容:对应列的数据类型应当兼容或可以隐式转换。
- 列名:最终结果集使用第一个 SELECT 语句的列名。
下面的表格直观展示了两者的区别:
| 特性 | UNION | UNION ALL |
|---|---|---|
| 重复行处理 | 自动过滤,结果集中每行唯一 | 保留所有重复行 |
| 性能 | 较慢,需要额外的排序和去重操作 | 很快,直接合并结果集 |
| 排序 | 默认会进行排序(用于去重) | 不保证顺序(除非显式 ORDER BY) |
| 适用场景 | 需要纯净、无重复数据的合并结果 | 允许或期望保留所有记录,强调性能 |
二、通过示例理解去重行为
假设有两张表 employees_cn 和 employees_us,记录员工姓名。
employees_cn
| name |
|---|
| 张三 |
| 李四 |
| 王五 |
employees_us
| name |
|---|
| 王五 |
| Alice |
| Bob |
使用 UNION ALL
SELECT name FROM employees_cn
UNION ALL
SELECT name FROM employees_us;
结果(保留 6 行,重复的“王五”出现两次):
张三
李四
王五
王五
Alice
Bob
使用 UNION
SELECT name FROM employees_cn
UNION
SELECT name FROM employees_us;
结果(仅保留 5 行,“王五”被去重):
张三
李四
王五
Alice
Bob
三、性能差异与执行原理
UNION 之所以比 UNION ALL 慢,是因为它在幕后做了更多工作:
-
UNION ALL 执行流程:
- 依次执行每个 SELECT 语句。
- 将返回的所有行直接拼接到结果集中。
- 无任何额外操作,因此速度很快。
-
UNION 执行流程:
- 分别执行所有 SELECT 语句。
- 将全部行放入临时表(或直接进行排序比较)。
- 对合并后的数据集执行
DISTINCT操作(通常通过排序或哈希方式),过滤掉完全相同的数据行。 - 这个过程涉及额外的 CPU、内存以及可能产生临时磁盘 I/O,在数据量较大时开销显著。
重要提示:UNION 的去重是基于 整行 的判断。所有选中的列组合在一起完全相同时,才会被视为重复行。
四、排序规则的区别
- UNION:由于要去重,MySQL 通常会对结果进行排序(在比较过程中)。如果没有外层
ORDER BY,最终返回的顺序可能看起来是按某列“自然”排序的,但这不是保证的行为,依赖它是不安全的。 - UNION ALL:仅做简单拼接,行顺序与各 SELECT 语句的执行顺序和数据库实际检索顺序有关,同样不具确定性。如果需要排序,必须显式使用
ORDER BY。
对整个合并结果排序(语句末尾加 ORDER BY):
SELECT name, '中国' AS region FROM employees_cn
UNION ALL
SELECT name, '美国' FROM employees_us
ORDER BY name;
注意:ORDER BY 只能出现在最后一条 SELECT 语句之后,且会作用于整个合并结果。
五、如何选择:决策指南
| 情况 | 推荐操作 |
|---|---|
| 你肯定各 SELECT 结果集本身就没有重复,或你完全不关心重复 | 务必使用 UNION ALL,性能最优 |
| 你需要合并的结果中每一行都必须唯一 | 使用 UNION |
| 源表数据量巨大,但需要去重 | 尽量用 UNION;若性能无法接受,可尝试在应用层去重,或利用临时表优化 |
| 需要保留重复记录用于后续统计(如实数计数) | 必须使用 UNION ALL |
| 想通过 UNION 对两个子查询去重,但部分列需要忽略 | 可使用 UNION ALL 结合外层 GROUP BY 灵活控制 |
开发中非常常见的错误:默认使用 UNION 而不是 UNION ALL。除非业务明确要求去重,否则一律优先使用 UNION ALL 以获得最佳性能。
六、高级技巧与注意事项
1. 在子查询中使用 UNION ALL
UNION ALL 可嵌套,常用作派生表:
SELECT name, COUNT(*) AS cnt
FROM (
SELECT name FROM employees_cn
UNION ALL
SELECT name FROM employees_us
) AS combined
GROUP BY name
HAVING cnt > 1;
此查询可以找出在两个表中都出现过至少一次的名字。如果内层误用 UNION,那么 cnt 将永远是 1,导致逻辑错误。
2. UNION 与索引
UNION/UNION ALL 对各个 SELECT 语句的索引利用情况取决于各自的 WHERE 条件。合并之后的结果集没有索引。因此,若需对最终结果过滤或联接,考虑先物化为临时表或优化各子查询。
3. 数据类型严格性
当对应列的数据类型不完全相同时,MySQL 会进行隐式转换。例如:
SELECT id FROM table1
UNION
SELECT name FROM table2; -- 如果 id 是整数,name 是字符串,结果将统一为字符串
为避免意外,尽量让对应列的类型明确一致,或在 SELECT 时显式转换。
七、总结
- UNION ALL:合并所有行,性能高,保留重复。是处理大数据量合并的首选。
- UNION:合并并去重,性能低,结果集唯一。仅在确实需要去重时使用。
- 永远先考虑用
UNION ALL,当且仅当业务要求数据唯一时才改用UNION。 - 注意列数量、类型匹配,以及通过显式
ORDER BY控制最终输出顺序。
掌握了这些区别,你就能在 SQL 查询中做出正确的选择,同时兼顾结果的正确性与系统的执行效率。