必须用UNION而非多次SELECT的核心场景是结果集结构一致且业务逻辑属同一类数据不同来源,如分库分表查活跃用户或合并订单状态表;需严格对齐字段顺序与类型,去重需求明确时才用UNION,否则优先UNION ALL以避免隐式排序与去重开销。

MySQL 集合查询(UNION、UNION ALL、INTERSECT、EXCEPT)不是万能的去重或合并工具,用错场景反而拖慢查询、掩盖数据问题。
什么时候必须用 UNION 而不是多次 SELECT
核心判断点:结果集结构一致,且业务逻辑上属于「同一类数据的不同来源」。
- 用户表分库分表后需统一查活跃用户:
SELECT id, name FROM user_shard_1 WHERE last_login > '2024-01-01' UNION SELECT id, name FROM user_shard_2 WHERE last_login > '2024-01-01' - 订单状态分散在不同表(如
order_normal和order_refund),但前端需要统一列表展示,字段顺序和类型必须严格对齐 -
UNION会自动去重并排序,如果不需要去重,务必改用UNION ALL,否则隐式DISTINCT + ORDER BY开销极大 - MySQL 8.0.19+ 才原生支持
INTERSECT和EXCEPT,低版本得用INNER JOIN或NOT EXISTS模拟,别硬套语法
UNION ALL 性能比 UNION 高多少?
不是“高一点”,而是量级差异——尤其在百万级以上结果集。
-
UNION内部等价于UNION ALL + GROUP BY(或临时表去重),涉及磁盘临时表、排序、哈希计算 -
UNION ALL只做结果集拼接,无额外逻辑,执行计划里看不到Using temporary; Using filesort - 实测:两个各返回 50 万行的子查询,
UNION ALL耗时约 0.12s;UNION耗时 1.8s 以上(取决于内存配置) - 如果业务允许重复(比如日志归集、埋点上报合并),默认选
UNION ALL;去重要求必须明确来自业务,而非“怕出错就用 UNION”
字段类型不一致导致 UNION 报错或隐式转换
MySQL 对 UNION 各子句的列类型兼容性很敏感,报错往往不直观。
- 常见错误:
ERROR 1267 (HY000): Illegal mix of collations—— 字符集/校对规则不一致,比如utf8mb4_0900_as_cs和utf8mb4_general_ci混用 -
INT和VARCHAR同列位置会触发隐式转换,可能截断或转成 0,例如:SELECT 123 UNION SELECT 'abc'→ 第二列转成123(字符串转数字失败则为 0) -
解决方法:显式
CAST或CONVERT统一类型,例如:SELECT CAST(id AS CHAR) FROM t1 UNION SELECT name FROM t2 - 列别名只取第一个子句的,后续子句别名无效,别指望靠别名对齐字段语义
替代方案比 UNION 更合适的情况
集合操作是语法糖,不是性能银弹。很多场景用单表 JOIN 或应用层聚合更稳。
- 要查「A 表有但 B 表没有」的数据,优先写
LEFT JOIN ... WHERE b.id IS NULL,比SELECT ... EXCEPT SELECT ...兼容性好、执行计划更可控 - 多条件动态组合查询(比如搜索页的「价格区间 + 品类 + 标签」),用
OR/IN/ 条件拼接通常比多个UNION快,且便于加索引 - 子查询结果集很小(UNION 涉及 5 张表,不如应用层拉回内存做集合运算,避免 MySQL 多次全表扫描
-
UNION无法下推谓词(WHERE 条件不能下压到每个子查询内部优化),而单独写多个SELECT可分别走索引
SELECT id, title FROM news WHERE status = 1 AND created_at > '2024-01-01' UNION ALL SELECT id, title FROM articles WHERE status = 2 AND created_at > '2024-01-01';
真正麻烦的不是语法,是字段对齐、类型收敛、执行计划不可控这三件事。线上跑着的 UNION 查询,建议用 EXPLAIN FORMAT=TREE 看是否用了临时表——如果看到 materialize,基本就是性能隐患了。










