UNION ALL 性能优于 UNION,因其不执行去重与排序;UNION 强制 DISTINCT 操作,触发排序或哈希去重,且 NULL 被视为相等而合并;列数、类型、顺序须严格一致;ORDER BY 和 LIMIT 仅可置于整个查询末尾。

UNION ALL 几乎总是比 UNION 快,除非你明确需要去重 —— 这不是建议,是执行计划决定的硬事实。
UNION 会触发 DISTINCT + 排序,UNION ALL 不会
UNION 不是“加个关键字”那么简单。它背后强制执行 DISTINCT 操作,数据库必须:先收集全部子查询结果,再用排序或哈希方式去重。哪怕两个子查询各返回 10 行且毫无重复,UNION 仍会走完整去重流程。
UNION ALL 则是纯追加:子查询 A 输出一行就推一行,B 同理,不缓存、不比较、不排序。
- UNION 的执行计划里一定有
Distinct节点(MySQL 8.0+ 可通过EXPLAIN FORMAT=TREE看到) - UNION ALL 执行计划只有
Append,无额外计算节点 - 当结果集超过
sort_buffer_size(默认 256KB),UNION 会写磁盘临时文件,UNION ALL 完全避开这步
列数、类型、顺序必须严格一致,否则报错
两个 SELECT 用 UNION 或 UNION ALL 连接时,数据库不会自动对齐字段。它要求:
- 每个 SELECT 返回的列数必须相同
- 对应位置的列数据类型要兼容(比如
INT和TINYINT可隐式转换,但VARCHAR(10)和TEXT在某些版本可能报错) - 列名以第一个 SELECT 为准,后续 SELECT 的别名会被忽略
常见错误:Column count doesn't match 或 Illegal mix of collations。别指望靠 AS 改名来绕过类型检查 —— 类型对不上,UNION ALL 同样失败。
ORDER BY 和 LIMIT 只能放在最后,不能分段加
想给每个子查询单独排序?不行。UNION / UNION ALL 是集合操作,不是管道。以下写法非法:
SELECT id FROM t1 ORDER BY id DESC UNION ALL SELECT id FROM t2 ORDER BY id ASC
正确做法只有一种:把整个 UNION 结果当一个派生表,再套一层查询加 ORDER BY:
(SELECT id FROM t1) UNION ALL (SELECT id FROM t2) ORDER BY id DESC
注意:ORDER BY 必须在最外层,且只能引用最终结果集的列名或位置序号(如 ORDER BY 1)。如果用了 LIMIT,也必须和 ORDER BY 配套出现在末尾。
NULL 值在 UNION 中的去重逻辑容易误判
UNION 对 NULL 的处理遵循 SQL 标准:两个 NULL 被视为“相等”,会去重合并。比如:
SELECT NULL UNION SELECT NULL → 返回 1 行 NULL SELECT NULL UNION ALL SELECT NULL → 返回 2 行 NULL
这点常被忽略。如果你的业务逻辑依赖 NULL 出现次数(例如统计缺失值频次),用 UNION 就悄悄丢数据了。
更隐蔽的问题:当某列含 NULL,而其他列值完全相同时,整行仍会被去重 —— 即使你没意识到那列是 NULL。
真正该纠结的不是“用哪个”,而是“要不要去重”。如果源数据本身已唯一(比如主键 ID)、或业务允许重复、或去重该由应用层控制,那就别碰 UNION —— 直接上 UNION ALL,少踩一堆执行计划和 NULL 语义的坑。


















