union all 保留重复行,因其不执行去重、排序或建临时表,仅直接拼接结果集;而 union 默认隐式去重并排序,列数与类型必须兼容,否则报错1222或类型转换异常。

UNION ALL 本身就会保留重复数据,不需要额外设置——只要两个 SELECT 的列数一致、类型兼容,它就直接拼接,不筛、不排、不建临时表。
为什么 UNION ALL 会保留重复行?
因为它不做任何去重逻辑。不像 UNION 会隐式加 DISTINCT 并触发排序和临时表,UNION ALL 就是把两块结果集的行“摞在一起”。哪怕 SELECT id, name FROM t1 和 SELECT id, name FROM t2 返回完全一样的行,也会原样保留。
- 执行计划里看不到
Using temporary或Using filesort - MySQL/PostgreSQL/SQL Server 都遵循这一行为,是 SQL 标准定义
- 如果你发现重复没保留,大概率是数据本身没重复,或者字段对齐错了(比如顺序颠倒、类型隐式转换截断)
列数和类型不匹配时的典型报错
常见错误不是“去重失败”,而是根本跑不起来:
-
ERROR 1222 (21S01): The used SELECT statements have a different number of columns→ 检查每个SELECT的字段个数是否一致 -
Truncated incorrect DOUBLE value或空值 → 字符串列和数值列硬拼,比如SELECT '2024-01-01' UNION ALL SELECT 123 - 字段内容错位:第一个
SELECT name, age,第二个写成SELECT age, name→ 结果里姓名列出现数字,年龄列出现字符串
解决办法:显式对齐,用别名 + 类型转换,例如 CAST(created_at AS DATE) 或 CONVERT(VARCHAR(10), dt, 120)(SQL Server)。
ORDER BY 和 LIMIT 怎么作用于整个 UNION ALL 结果?
ORDER BY 和 LIMIT 不能直接跟在某个子查询后面(除 PostgreSQL 支持括号内子句外,MySQL/SQL Server 会报语法错误)。必须把整个 UNION ALL 包进派生表再操作:
SELECT * FROM ( SELECT id, name, 'old' AS source FROM users_old UNION ALL SELECT id, name, 'new' AS source FROM users_new ) AS combined ORDER BY id DESC LIMIT 100;
注意:ORDER BY id DESC 里的 id 必须是第一个 SELECT 中定义的列名(或别名),否则报错;如果不加外层包装,ORDER BY 只影响最后一个 SELECT 的局部输出,不是整体。
性能快在哪?但容易被忽略的关键点
快在跳过三件事:排序、哈希去重、临时表写入。10 万行 + 10 万行合并,UNION ALL 通常在 50ms 内完成,UNION 可能要 800ms+。
但真正容易踩坑的是字段对齐细节:
- 一侧是
VARCHAR(255),另一侧是TEXT→ 某些引擎会把整列升为TEXT,影响内存占用和后续ORDER BY效率 - 字符集或排序规则不同(如
utf8mb4_0900_as_csvsutf8mb4_general_ci)→ 报错或隐式转换失败,需加COLLATE - 列名以第一个
SELECT为准,但业务代码若依赖第二个查询的列名,可能出错
别只盯着“有没有重复”,先盯死字段顺序、类型宽度、排序规则这三样。










