UNION要求列数相同且对应列类型兼容,自动去重;UNION ALL不去重、性能更高,适用于无重复或需保留重复的场景。

UNION 要求列数和数据类型严格匹配
UNION 不是简单拼接,它会先去重再合并,而且要求左右两个 SELECT 的列数必须一致,对应位置的列类型要兼容(比如 INT 和 TINYINT 通常可隐式转换,但 VARCHAR(10) 和 TEXT 在某些数据库里可能报错)。常见错误是左边查了 3 列,右边只写了 2 列,直接抛出 ERROR 1222 (21S02): The used SELECT statements have a different number of columns。
实操建议:
- 先分别跑通两个
SELECT,确认字段数量、顺序、别名(别名以左查询为准) - 用
CAST()或CONVERT()显式统一类型,比如CAST(age AS SIGNED)防止字符串数字被当成文本比较 - 避免在子查询里混用
ORDER BY—— 只有最外层SELECT允许加ORDER BY
UNION vs UNION ALL:性能差异很实在
UNION 默认去重,内部会做排序 + 去重操作,开销明显;UNION ALL 直接追加,零额外成本。如果业务确定结果无重复,或者重复本身可接受(比如日志归档、分表汇总),必须用 UNION ALL。
典型场景:
- 从多个分表查订单:
SELECT * FROM orders_2023 UNION ALL SELECT * FROM orders_2024 - 合并测试环境和生产环境的配置项(ID 不重叠)
- 临时补数据时明确知道不会重复,硬上
UNION反而拖慢响应
ORDER BY 和 LIMIT 必须放在最后
很多人想对每个子查询单独排序,写成 (SELECT ... ORDER BY id) UNION (SELECT ... ORDER BY id),这语法不合法,MySQL 会报 Syntax error near 'UNION'。正确做法是把整个联合结果当做一个派生表,或直接在外层加 ORDER BY。
示例:
SELECT * FROM ( SELECT id, name FROM users WHERE dept = 'tech' UNION ALL SELECT id, name FROM users_backup WHERE dept = 'tech' ) AS combined ORDER BY id DESC LIMIT 10;
注意:LIMIT 如果只写一个数字(如 LIMIT 5),作用于整个联合结果;若需分页,得配合 OFFSET,且 ORDER BY 必须存在,否则行为不可靠。
NULL 值参与 UNION 去重时容易被忽略
UNION 去重逻辑中,NULL = NULL 被视为真(标准 SQL 行为),所以两行都含 NULL 的字段会被当成重复行合并掉。如果你的业务依赖 NULL 区分不同记录(比如未填写的地址 vs 明确为空字符串),用 UNION 就可能丢数据。
应对方式:
- 提前用
COALESCE(col, '___NULL___')把NULL转成确定值,再UNION - 改用
UNION ALL+ 应用层去重(更可控) - 检查是否真需要去重 —— 很多报表类需求其实允许重复,只是前端展示要去重
真正麻烦的是跨数据库行为不一致:PostgreSQL 严格遵循标准,MySQL 8.0+ 也一样,但旧版 MySQL 对 NULL 处理偶有偏差,上线前得实测。

















