默认应使用 union all,因其仅拼接结果集、无去重排序开销,性能通常是 union 的 5~10 倍;union 会隐式建临时表、排序去重,易触发 using temporary/using filesort,且在 null、collation、浮点精度等场景下可能误合并行。

默认用 UNION ALL,除非你明确需要去重;UNION 会隐式去重+排序,容易拖慢查询、触发临时表,多数场景是过度设计。
为什么 UNION ALL 是更安全的默认选择
MySQL 执行 UNION 时,会在内存或磁盘建临时表,全量扫描+排序+去重,大结果集下极易报 Using temporary; Using filesort,甚至 OOM。而 UNION ALL 就是逐行追加,执行计划干净,耗时通常只有 UNION 的 1/5~1/10。
常见误判场景:
- “两个表主键不重叠,用
UNION应该没问题”——但字段含NULL、浮点精度差异、字符集 collation 不一致时,UNION可能意外合并本不该合并的行 - “只是想拼起来看看”,却因
UNION隐式按第一列升序,打乱了原始时间顺序
UNION ALL 列对齐的硬性要求
报错 ERROR 1222 (21000): The used SELECT statements have a different number of columns 是最常见拦路虎。这不是警告,是直接中断。
- 每个
SELECT的列数必须完全相等,不能靠SELECT *蒙混——表结构一变就崩 - 列顺序必须一致:第一个
SELECT是id, name, created_at,后面所有都得严格按此顺序,哪怕某表没有created_at,也得写NULL AS created_at - 类型要兼容:允许
TINYINT→INT、VARCHAR(10)→VARCHAR(100),但DATE和INT强行 union 可能返回NULL或截断值 - 字段名以第一个
SELECT为准,后续子句里的别名会被忽略,想统一语义必须在第一个里定义,如SELECT id AS user_id, name AS full_name FROM t1
ORDER BY 和 LIMIT 只能放在最后
UNION ALL 是集合操作符,不是容器。中间任何一个 SELECT 单独加 ORDER BY 或 LIMIT 都会直接报语法错误(尤其 MySQL 8.0 以前版本)。
- 整体排序:写在末尾,如
UNION ALL SELECT ... ORDER BY created_at DESC - 整体分页:同理,
UNION ALL SELECT ... LIMIT 20 OFFSET 10 - 某一边要取最新 5 条?必须包成子查询:
(SELECT * FROM logs WHERE type = 'error' ORDER BY ts DESC LIMIT 5) UNION ALL (SELECT * FROM logs WHERE type = 'warn' ORDER BY ts DESC LIMIT 5)
什么时候真该用 UNION 而不是 UNION ALL
只有两类情况值得切换:
- 数据源天然可能重叠,且业务逻辑要求绝对唯一——比如合并两份用户导入名单,需防重复注册
- 下游系统或报表工具对重复行异常敏感,且无法在应用层 dedup
其他时候,UNION 带来的性能损耗和行为不确定性,远大于它省下的那几行代码。











