union all 不去重、不排序,仅顺序拼接结果集;union 会隐式去重并按第一列升序排序,带来临时表、排序等额外开销,性能显著更低,90%以上场景应优先选用 union all。

UNION ALL 和 UNION 的核心区别在哪
UNION ALL 不去重、不排序,只是把多个 SELECT 的结果集按顺序拼接;而 UNION 会隐式去重 + 排序,代价高且行为不可控。如果你确定数据无重复、或根本不需要去重(比如日志分表查询、多来源汇总),必须用 UNION ALL,否则性能可能差几倍。
- 去重操作会触发临时表和排序,尤其在大结果集上明显拖慢响应
-
UNION的隐式排序是按字段顺序升序,无法指定,也容易引发意料外的行序变化 - 多数 OLAP 场景(如报表取数、ETL 拉取)只关心“合并”,不用
UNION
列数、类型、顺序必须严格一致
UNION ALL 要求每个 SELECT 返回相同数量的列,对应位置的列类型需兼容(如 INT 和 BIGINT 可隐式转换,但 VARCHAR 和 JSON 在某些数据库中会报错),且别名以第一个 SELECT 的列名为准。
- 后续
SELECT中不能多列或少列,否则报错:ERROR: each UNION query must have the same number of columns - 类型不兼容时,PostgreSQL 会拒绝执行;MySQL 可能强制转成字符串导致精度丢失(比如把
DECIMAL转成DOUBLE) - 别名写在第一个
SELECT就够了,后面SELECT的列名会被忽略
SELECT id, name, 'user' AS source FROM users UNION ALL SELECT user_id AS id, full_name AS name, 'admin' AS source FROM admins;
ORDER BY 和 LIMIT 只能加在最后
ORDER BY 和 LIMIT 不能出现在单个 SELECT 子句里(除 PostgreSQL 支持子查询带 LIMIT 外),只能在整个 UNION ALL 语句末尾使用。想对某一部分先排序再合并?得包一层子查询。
- 直接写
SELECT ... ORDER BY x UNION ALL SELECT ...会报语法错误 - 如果需要“取每张表最新 10 条再合并”,得这样:
(SELECT * FROM logs_202401 ORDER BY created_at DESC LIMIT 10) UNION ALL (SELECT * FROM logs_202402 ORDER BY created_at DESC LIMIT 10) ORDER BY created_at DESC LIMIT 20;
- 注意:外层
ORDER BY才真正决定最终顺序,内部子查询的排序仅服务于其自身的LIMIT
NULL 值和隐式类型转换容易踩坑
当不同 SELECT 中同一列有 NULL 和非 NULL 值时,数据库会按第一个 SELECT 的列类型推断该列类型。如果第一个是 NULL,某些数据库(如 SQLite)会把它当成 TEXT,后续整数列就可能被转成字符串。
- MySQL 8.0+ 对
NULL类型推断更严格,常报Illegal mix of collations或类型冲突 - 显式 cast 更安全:把
NULL写成CAST(NULL AS INTEGER)或NULL::INT(PostgreSQL) - 实际写法建议统一补全类型,尤其跨表字段名不一致时:
SELECT id::BIGINT, name::TEXT, created_at::TIMESTAMP FROM users UNION ALL SELECT CAST(user_id AS BIGINT), CAST(full_name AS TEXT), CAST(updated_at AS TIMESTAMP) FROM admins;
真实场景里,最麻烦的往往不是语法,而是各 SELECT 的列语义是否真的一致——比如一个 amount 是含税价,另一个是税前,合并后数字看起来对,逻辑却错了。










