绝大多数时候该用 UNION ALL,因其不去重、不排序、性能远高于 UNION;UNION 会隐式去重并默认排序,带来额外开销,仅在确需去重且结果唯一时使用。

UNION 去重且隐式排序,UNION ALL 不去重、不排序、性能高得多——绝大多数时候该用 UNION ALL,除非你明确需要去重。
UNION 会自动执行 DISTINCT + ORDER BY
很多开发者以为 UNION 只是“去重”,其实它背后还隐含一次默认排序(通常按第一列升序),这是 SQL 标准允许但各数据库实现略有差异的行为。比如在 PostgreSQL 和 SQL Server 中,UNION 结果确实可能按首列排序;而 MySQL 8.0+ 在无 ORDER BY 时并不保证顺序,但去重逻辑仍依赖内部排序或哈希去重。关键点在于:UNION 等价于先 UNION ALL 再套一层 SELECT DISTINCT ...,额外开销逃不掉。
- 执行计划里常出现
Using temporary和Using filesort(MySQL) - 即使两结果集本身已排好序,
UNION仍会重新排序去重 - 如果列含
TEXT、JSON或大对象类型,某些数据库(如 MySQL)可能直接报错,因无法比较
UNION ALL 是纯拼接,零额外计算
UNION ALL 就是把多个 SELECT 的结果按顺序堆叠起来,不做任何比对、不建临时表、不触发排序。它的输出顺序 = 第一个 SELECT 的行 + 第二个 SELECT 的行 + ……,完全可控。
- 100 万行数据合并时,
UNION ALL比UNION快 10 倍以上(实测常见) - 字段含
NULL不影响行为:两个NULL在UNION中视为重复,在UNION ALL中就是两条独立记录 - 跨库或分表查询时,只要主键/业务键天然不重(如按时间分片的日志表),
UNION ALL是唯一合理选择
列数、类型、顺序必须严格一致
无论用哪个,所有 SELECT 子句的列数必须相同,对应位置的列需兼容(例如 INT 和 TINYINT 可隐式转换,但 VARCHAR(10) 和 TEXT 在某些场景会失败)。列名以第一个 SELECT 为准,后续子句的别名无效。
- 错误示例:
SELECT id, name FROM a UNION SELECT user_id FROM b→ 列数不等,直接报错ERROR 1222 (HY000): The used SELECT statements have a different number of columns - 隐式转换陷阱:PostgreSQL 对类型更严格,
SELECT 1 UNION ALL SELECT '1'会失败;MySQL 可能转成字符串但丢失精度 - 排序规则冲突(如不同
COLLATE)会导致UNION报错,UNION ALL同样受影响,需显式加COLLATE utf8mb4_unicode_ci
什么时候非用 UNION 不可?
真正需要 UNION 的场景极少,集中在“数据源不可信 + 结果必须唯一”:比如合并多个 CRM 系统导出的客户名单,ID 字段命名不一(cust_id / client_no),又没做清洗,这时靠 UNION 救急。但生产环境更推荐前置清洗或用 UNION ALL + 外层 GROUP BY 控制去重逻辑——至少你能看清去重依据。
- 别用
UNION替代DISTINCT:写成SELECT DISTINCT col FROM (SELECT col FROM t1 UNION ALL SELECT col FROM t2) AS u更清晰 - 分页时尤其小心:
(SELECT * FROM t1 LIMIT 10) UNION (SELECT * FROM t2 LIMIT 10)的总数不一定是 20,且顺序不可控 - MyBatis 等 ORM 中拼
UNION动态 SQL 时,容易漏掉类型对齐检查,上线后才暴露
最易被忽略的点:很多人在视图或 CTE 里写 UNION,以为只是语法糖,却没意识到它让整个上游查询失去索引下推能力——哪怕子查询本身能走索引,UNION 的去重阶段也会打断执行链。能用 UNION ALL 就别省那几行代码。


















