union all 不去重、不排序、速度快,适合数据互斥场景;union 会隐式去重排序,性能差3–5倍,易触发临时表和内存溢出。

UNION ALL 和 UNION 有什么实际区别?
用 UNION ALL 拼接多张表时,数据库不检查重复行、不排序、不额外去重,所以速度明显快于 UNION。如果你确定各表数据互斥(比如按日期分表、按业务线分表),或者根本不需要去重,UNION ALL 是唯一合理选择。用 UNION 反而会触发隐式排序和临时表写入,尤其在百万级以上数据量时,耗时可能差出 3–5 倍。
常见错误现象:UNION 执行缓慢、执行计划里出现 Using temporary; Using filesort、内存溢出报错 MySQL server has gone away。
使用场景举例:
- 日志表按天切分(
log_20240101,log_20240102...) - 不同区域销售数据表(
sales_north,sales_south) - ETL 后的中间结果表(
stg_order_v1,stg_order_v2)
列名、顺序、类型必须严格一致才能拼接
UNION ALL 不关心表名,只按 SELECT 子句中字段的**位置顺序**匹配列。第 1 张表的第 1 列,必须和第 2 张表的第 1 列类型兼容(如 INT 和 TINYINT 可隐式转换,但 VARCHAR(10) 和 TEXT 在某些 MySQL 版本会报错),否则直接报错或截断。
实操建议:
- 显式写出所有字段名,不要用
*—— 避免因某张表新增字段导致列数不匹配 - 用
CAST()或CONVERT()统一关键字段类型,例如把user_id全部转成BIGINT - 为缺失字段补
NULL占位,并加类型别名(NULL AS status),避免隐式推导失败 - 字段别名以第一个
SELECT的为准,后续子句的别名会被忽略
如何给拼接结果打上来源标记?
纯 UNION ALL 拼完后无法区分某行来自哪张表。生产中几乎都需要加来源标识,否则排查问题或做分源统计就抓瞎。
正确做法是在每张表的 SELECT 中硬编码一个常量字段:
SELECT 'order_2024_q1' AS source, order_id, amount FROM order_2024_q1 UNION ALL SELECT 'order_2024_q2' AS source, order_id, amount FROM order_2024_q2
注意点:
- 字符串常量要用单引号,不能用双引号(部分数据库如 PostgreSQL 对双引号敏感)
- 字段名(如
source)必须和第一个子句一致,否则报错 - 如果某张表本身有
source字段,要显式重命名,例如source AS orig_source,再补新标识
大表拼接时性能卡在哪?怎么绕开?
当单表超千万行、拼接 5 张以上时,UNION ALL 本身不慢,但后续操作容易拖垮整体:比如外面套一层 ORDER BY 或 GROUP BY,会导致全部结果集先落磁盘再计算;又或者 WHERE 条件没下推到各子句里,变成全表拼完再过滤。
优化关键点:
- 把过滤条件尽量写进每个子句(如
WHERE created_at >= '2024-01-01'),而不是拼完再加WHERE - 避免在
UNION ALL外层直接ORDER BY,如需排序,优先考虑应用层归并,或改用分区表 + 全局索引 - MySQL 8.0+ 可结合 CTE +
MATERIALIZED提示控制物化时机,但要注意临时空间占用 - PostgreSQL 用户可考虑
UNION ALL+TABLESAMPLE快速抽样,避免全量扫描
最容易被忽略的是字段类型隐式转换——看着都是整数,但一张是 INT UNSIGNED,另一张是 INT SIGNED,MySQL 会统一转成 BIGINT 并全表重算,这种细节不看执行计划根本发现不了。










