UNION会自动去重并排序,但性能差;UNION ALL仅拼接结果、不去重不排序,效率高。两者字段数量、顺序、类型须严格一致,语义和性能需求决定选型。

UNION 会自动去重并排序,但代价是性能下降
直接用 UNION 合并两个查询,数据库会强制执行去重(等价于加了 DISTINCT)和默认排序。这意味着即使你只想要“不重复的行”,它也会先全量拉出所有数据,再建临时表、排序、逐行比对——这个过程在 MySQL 中常表现为 Using temporary 和 Using filesort,尤其在百万级数据时延迟飙升。
- 去重不是“智能跳过”,而是暴力比较:字段类型、值、NULL 都要严格一致才算重复
- 排序规则由字段类型决定,比如
VARCHAR按字符集 collation 排,可能和你预期不一致 - 如果子查询里已有
ORDER BY,UNION会忽略它;最终排序必须写在外层ORDER BY
UNION ALL 不去重也不排序,快但结果可能含重复
UNION ALL 就是把两个结果集顺序拼起来,不做任何额外处理。它不检查重复、不排序、不建临时表,I/O 和 CPU 开销都极低。TPC-H 测试显示,100 万行数据下 UNION ALL 耗时约 810ms,而 UNION 达到 9200ms,相差超 11 倍。
- 字段数量、顺序、类型必须完全一致,否则报错
ERROR 1222 (21000): The used SELECT statements have a different number of columns - 允许
NULL和非NULL混合,但类型隐式转换可能引发意外截断(如VARCHAR(10)和VARCHAR(50)合并时按短的截) - 如果两个子查询结果天然无重(比如查的是不同日期分区表),
UNION ALL是唯一合理选择
什么时候该用 UNION,什么时候必须用 UNION ALL
别凭感觉选,看数据语义和性能要求:
- 需要业务上“唯一记录”且无法保证源头不重 → 用
UNION,例如合并用户注册表和测试账号表,怕同一手机号出现两次 - 查的是分片表、历史表 + 当前表、或已用
WHERE精确隔离数据范围 → 直接用UNION ALL,例如SELECT * FROM order_2025_q4 UNION ALL SELECT * FROM order_2026_q1 - 后续还要
GROUP BY或JOIN→ 优先UNION ALL,因为去重留到聚合阶段更可控,也避免UNION的中间排序干扰索引使用 - 开发环境小数据无所谓,但上线前必须查
EXPLAIN看有没有Using temporary—— 有就说明UNION正在拖慢你的查询
常见错误:字段不匹配、括号缺失、别名位置错
这些不是语法警告,是直接报错,而且错误信息往往不指向根本原因:
-
SELECT id,name FROM t1 UNION SELECT id FROM t2→ 报错,列数不等,不是“自动补 NULL” -
(SELECT a FROM t1) UNION (SELECT b FROM t2 ORDER BY b)→ 外层没ORDER BY时,子查询的ORDER BY无效,还可能被优化器忽略 -
SELECT * FROM (SELECT id FROM t1 UNION ALL SELECT id FROM t2) AS tmp WHERE id > 100→ 正确;但漏掉AS tmp别名会报Every derived table must have its own alias - 混合使用
UNION和UNION ALL时,优先级从左到右,没括号容易误判逻辑,建议统一用括号包裹每个子查询
实际执行时,最易被忽略的是字段隐式类型转换带来的重复判断失效——比如一个子查询返回 INT,另一个返回 DECIMAL(10,0),MySQL 会转成浮点比较,导致本该去重的整数行被当成不同值保留。这种问题不会报错,但结果不对,得靠人工核对样本数据才能发现。


















