LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

UNION 和 UNION ALL,你的 SQL 慢一倍,可能就是多写了一个all

admin
2026年4月13日 22:35 本文热度 14

UNION 和 UNION ALL,差了一个 ALL。这个 ALL 省掉的,不只是一个关键字——是一整趟排序去重的成本。


两个查询,逻辑一模一样:

SELECT user_id FROM orders
UNION

SELECT
 user_id FROM users;
SELECT user_id FROM orders
UNION
 ALL
SELECT
 user_id FROM users;

区别只有一个字。

但当数据量到了百万级,UNION 那条会慢到你怀疑人生。

很多人写着写着,觉得 UNION "更规范"、"更干净",
结果每次跑报表都比别人慢,还不知道为什么。

今天把这件事说透。


一、UNION 和 UNION ALL 本质差在哪

UNION:合并 + 去重

UNION 会做两件事:

  1. 1. 把两个结果集合并
  2. 2. 对合并后的结果做去重(DISTINCT)

去重怎么做的?
对所有列做排序,然后相邻行逐行比较,相同的只留一条。

这一排序,在数据量大的时候,是真的贵。

UNION ALL:只合并,不去重

UNION ALL 就简单了:

直接把两个结果集首尾拼接在一起,挨个输出,不做任何额外处理。


二、性能差距有多大

说数字最清楚。

假设 orders 有 200 万行,users 有 50 万行。

-- UNION:先去重再输出
SELECT
 user_id FROM orders
UNION

SELECT
 user_id FROM users;
-- 执行大概 3~5 秒(取决于硬件)


-- UNION ALL:直接拼接输出

SELECT
 user_id FROM orders
UNION
 ALL
SELECT
 user_id FROM users;
-- 执行大概 1~2 秒(还是取决于硬件)

表面上是快一倍。
实际场景里,如果列更多、数据更碎,差距可以更大。

核心原因就一句:

UNION 的去重成本,随数据量增长是非线性的。


三、五个最常见的误用场景

误用 1:查多个表的同期用户,以为自己在"去重"

-- 很多人以为 UNION 自动帮他们去重,
-- 于是这么写:

SELECT
 user_id, login_date
FROM
 app_login_2025
UNION

SELECT
 user_id, login_date
FROM
 app_login_2024;

问题在哪?

UNION 去重是整行完全相同才去掉
如果同一用户在不同年份的登录日期不同,整行就不一样,
UNION 根本不会去重。

结果:用户重复出现,统计 DAU 反而偏高。

误用 2:以为 UNION 会自动选"更好"的数据

SELECT user_id, '2025' AS year
FROM
 users_2025
UNION

SELECT
 user_id, '2024' AS year
FROM
 users_2024;

这个其实是对的,因为加了 year 列标签之后两边的行本来就不一样。

但很多人把 UNION 当成"合并同类项"的工具,
一旦忘记加区分列,去重逻辑就完全不是你以为的那样。

误用 3:UNION 后发现结果变少了,百思不得其解

这是最让人崩溃的一种。

SELECT user_id FROM orders
UNION

SELECT
 user_id FROM users;

结果数量比预期少。

原因很简单:
如果某个 user_id 同时出现在 orders 和 users 表里,
UNION 就会把它合并成一条。

这不是 bug,是设计逻辑。
但很多人根本不知道,直到报表数据莫名其妙变少。

误用 4:UNION 搭配 ORDER BY,性能灾难

SELECT user_id, order_time
FROM
 orders
WHERE
 order_time >= '2025-01-01'
UNION

SELECT
 user_id, login_time
FROM
 users
WHERE
 login_time >= '2025-01-01'
ORDER
 BY user_id;

这条 SQL 实际执行顺序是:

  1. 1. 分别查两个表
  2. 2. 合并所有结果
  3. 3. 对整个合并结果做全局排序

意味着:排序的数据量 = A 表结果 + B 表结果
而不是分别排序再合并。

数据量大的话,这个全局排序会吃大量内存,可能触发临时文件。

误用 5:嵌套子查询里用 UNION,不知道外层 ORDER BY 对谁生效

SELECT * FROM (
  SELECT
 user_id, 'order' AS src
  FROM
 orders
  UNION
 ALL
  SELECT
 user_id, 'user' AS src
  FROM
 users
) t
ORDER
 BY user_id;

这里有个细节:

子查询里的 UNION ALL 先执行,
外层的 ORDER BY 是对整个子查询结果排序。

这是合理的,但如果把 ORDER BY 写在 UNION 的某个分支里——
对不起,外层 ORDER BY 依然对整表生效,
子查询里的 ORDER BY 基本被忽略(取决于数据库实现)。


四、什么场景必须用 UNION,不能用 UNION ALL

有些场景,UNION 是对的,不能省。

场景 1:需要全局去重的结果集

-- 查所有活跃用户(包括有订单的和已注册的)
SELECT
 user_id FROM orders
UNION

SELECT
 user_id FROM users;

这里你确实希望去重
同一个 user_id 只出现一次。
那就用 UNION,这是正确用法。

场景 2:需要合并多个相关但不同的来源

SELECT '大促活动' AS campaign, product_id, sales
FROM
 promotion_sales
UNION

SELECT
 '日常销售' AS campaign, product_id, sales
FROM
 daily_sales;

加了一个 campaign 标签列,两边结果天然不同,
UNION 去重在这个场景下是合理的额外保障。

场景 3:为了代码简洁,牺牲一点性能

有些场景下,逻辑上明明知道不会有重复,
但 UNION 写起来比 UNION ALL + 额外逻辑更清晰。

这时候用 UNION 不算错,只是要知道性能代价。


五、什么场景坚决用 UNION ALL

以下场景,不要碰 UNION:

场景 1:已知两表数据完全不重叠

-- 2024 年用户和 2025 年用户,通常不会有交集
SELECT
 user_id, '2024' AS year
FROM
 users_2024
UNION
 ALL
SELECT
 user_id, '2025' AS year
FROM
 users_2025;

场景 2:需要保留所有明细,逐行分析

-- 拉清单:每个用户每个渠道的活跃记录
SELECT
 user_id, channel, active_date
FROM
 app_channel_login
UNION
 ALL
SELECT
 user_id, channel, active_date
FROM
 web_channel_login;

这里去重会丢失"同一个用户多个渠道"的信息,
只能用 UNION ALL。

场景 3:UNION ALL + GROUP BY,比 UNION 后聚合更高效

-- 正确写法:先合并明细,再统一聚合
SELECT
 user_id, COUNT(*) AS cnt
FROM
 (
  SELECT
 user_id FROM orders
  UNION
 ALL
  SELECT
 user_id FROM returns
) t
GROUP
 BY user_id;

注意:这里子查询用 UNION ALL,
外层 GROUP BY 一次性搞定所有统计。

如果反过来:

-- 错误思路:两个聚合再 UNION
SELECT
 user_id, SUM(order_cnt) AS total_cnt
FROM
 (
  SELECT
 user_id, COUNT(*) AS order_cnt
  FROM
 orders
  GROUP
 BY user_id
  UNION

  SELECT
 user_id, COUNT(*) AS order_cnt
  FROM
 returns
  GROUP
 BY user_id
) x
GROUP
 BY user_id;

这个写法又慢又绕,
因为你做了两次聚合再合并,不如第一种。


六、一句话判断原则

以后写 SQL,遇到 UNION 还是 UNION ALL,
先用这个判断:

这两个结果集,有没有可能是同一个 user_id / 同一行?

  • • 有可能是 → 用 UNION(需要去重)
  • • 绝对不可能是 → 用 UNION ALL(直接拼接)
  • • 不确定 → 先想清楚,再决定

七、最后总结


UNION
UNION ALL
去重
✅ 会去重
❌ 不去重
性能
慢(需排序)
快(直接拼接)
结果行数
≤ 两表之和
= 两表之和
NULL 处理
NULL = NULL 不成立,去重逻辑复杂
简单拼接
适用场景
确实需要去重
已知无重复,或需保留所有明细

记住:UNION ALL 是默认选项,UNION 是例外。

不要因为 UNION "看起来更干净",就默认用它。
多写一个 ALL,省掉的可能是一整趟排序,和一张报表的等待时间。

下次写 SQL 之前,先问自己一句: 我真的需要去重吗?

这个问题,值得你多花三秒钟。


阅读原文:原文链接


该文章在 2026/4/14 16:17:57 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-2  粤公网安备44030602007207号