UNION与UNION ALL的区别:去重原理、性能对比与优化实践
2026/9/24 19:49:58 网站建设 项目流程

说实话,UNION 和 UNION ALL 这两个关键字,几乎每个写 SQL 的人都认识,但它们之间的差别绝不只是“去重”和“不去重”这么简单。我在日常做报表汇总、跨表对账、数据清洗的时候,这两个操作符几乎是天天见,但真正能把它们的执行原理、性能差异、隐藏的坑讲清楚的人并不多。很多人线上慢 SQL 一查,发现罪魁祸首就是那个随手写的 UNION,或者反过来,业务上明明需要去重,却为了图快用了 UNION ALL,结果金额翻倍、统计失真。

这篇文章我就从原理到实战,把这两个合并操作彻底拆开揉碎讲一遍。我会先用最简单的话说明它们是什么、能解决什么问题,再讲背后的执行逻辑,然后给出一组实测数据和优化方案,最后把我在项目里踩过的一些坑整理成清单。不管你是刚入门的新手,还是经常和数据打交道的开发、分析师,看完应该都能少走不少弯路。

1. UNION 和 UNION ALL 到底在做什么

1.1 核心差异:一个去重,一个不去重

先从一个最直观的例子说起。假设你有一张 2023 年订单表,里面有订单号 1、2、3;另一张是 2024 年订单表,里面有订单号 3、4、5。现在你想把两年的订单合并到一起看,于是分别写下两条 SQL:

SELECT order_no FROM orders_2023 UNION SELECT order_no FROM orders_2024;

结果会返回 1、2、3、4、5 共 5 行,因为订单号 3 在两年的表里都出现了,UNION 会自动帮你把重复的那一条去掉。

而如果把 UNION 换成 UNION ALL:

SELECT order_no FROM orders_2023 UNION ALL SELECT order_no FROM orders_2024;

结果会返回 1、2、3、3、4、5 共 6 行,订单号 3 出现了两次,因为 UNION ALL 什么都不管,就是把两块数据上下拼在一起。

一句话总结:UNION 会对最终结果集做一次去重,UNION ALL 原样拼接。这里说的“重复”,指的是整行数据在每个字段上的取值完全一致,只要有一个字段不同,就不会被当作重复行去掉。

这个区别看起来很简单,但很多人不知道的是,为了做“去重”这个动作,数据库在背后要付出相当大的代价。我见过太多性能问题,根源就是在一张几千万行的表上,毫不犹豫地用了 UNION,结果排序缓存被打爆,临时文件写满磁盘,一个本来 1 秒能出的查询硬生生拖到几十秒。

1.2 为什么“去重”会让性能差别这么大

要理解 UNIUN 为什么慢,得先明白数据库是怎么实现“去重”的。标准 SQL 里对结果集做去重,最常见的手段有两种:一种是排序去重,一种是哈希去重。

排序去重的思路大致是这样的:先把合并后的所有数据放进一个临时结果集,然后按照所有列的值排个序。排完序之后,相同的数据自然就挤在一起了,数据库只需要从头到尾扫一遍,把相邻的重复行丢掉就行。这样做的好处是逻辑简单、实现稳定,坏处是排序本身非常昂贵。这里我就不堆时间复杂度公式了,大家只需要记住一句直觉:当数据量从一万涨到一千万,排序的开销不是涨一千倍,而是涨得更多,因为数据太多时内存放不下,就得往磁盘写临时文件,来回读写磁盘比内存操作慢几个数量级。

哈希去重好一些,在内存充足的情况下,它只需要把每行数据算出一个哈希值,放进哈希表里,遇到重复的哈希值再做精确比较。理想情况下是线性增长的开销,比排序快很多。但哈希表同样吃内存,数据量大到一定程度,依然逃不过落盘的命运。

你可以用数据库的执行计划来验证这一点。在 MySQL 里对一个大查询做 EXPLAIN,如果看到了 Using temporary 或者 Using filesort,基本可以断定这里发生了隐式的排序去重。在 SQL Server 的执行计划里,UNION 对应的则是 Distinct Sort 这个操作符,名字起得非常直白:先合并,再去重排序。而 UNION ALL 在执行计划里通常只有一个 Concatenation 节点,中文叫串联,就是把两个数据流直接接起来,没有任何额外处理。

这就是为什么 UNION ALL 通常比 UNION 快好几倍。它快不是因为它用了什么黑魔法,而是它真的什么额外的事都没做。

1.3 从业务角度判断该用哪一个

既然 UNION ALL 快这么多,那是不是可以无脑用 UNION ALL?千万别。我见过不少新手这么干,然后被测试数据狠狠教育了一顿。选哪个操作符,第一优先级不应该是性能,而是业务语义。

我自己的判断流程很简单,先问自己三个问题:

第一,这两个结果集在业务上有没有可能重叠?比如一个是 2023 年的订单,一个是 2024 年的订单,两者用时间字段天然隔离,主键不会重合,那基本可以放心用 UNION ALL。

第二,如果存在重复行,重复计入会不会影响最终的业务结论?举个例子,查询“每个销售顾问的成单量”,同一个订单如果既出现在 A 表又出现在 B 表,UNION ALL 会把单子数算两遍,这种就必须用 UNION,或者先做一步去重。

第三,为了去重付出的性能代价,是否能换来正确性?如果答案是肯定的,那就老实使用 UNION,别为了省那几秒钟把数据搞错。

还有一种比较折中的情况:两个结果集可能会重叠,但重叠率很低。比如只有 1% 的数据重复,你却用 UNION 对全量数据做一次大排序,这其实是一种浪费。后面我会专门讲怎么处理这种场景。

2. 合并操作的语法细节与易错点

2.1 列数、列顺序与列名的对齐规则

UNION 和 UNION ALL 的基本语法很简单,就是把多个 SELECT 语句用关键字连接起来。但有一条规定是硬性的:每个 SELECT 返回的列数必须完全一致。比如第一个 SELECT 查了三列,第二个 SELECT 也得查三列,否则数据库直接报错,这没得商量。

这里有一个特别容易犯的错:列数一样就够了?不够,还要注意列的顺序。UNION 是按位置对齐的,也就是说,第二个 SELECT 的第一列会跟第一个 SELECT 的第一列对应,第二列跟第二列对应,跟列名完全无关。

举个例子,第一个 SELECT 返回的是 order_id, user_id, amount,第二个 SELECT 返回的是 user_id, amount, order_id,虽然列名和数据类型看起来都能对得上,但如果你直接 UNION,数据库会把第二个 SELECT 的 user_id 放到第一个 SELECT 的 order_id 这一列去比较和拼接。这种情况下,轻则隐式转换造成性能问题,重则结果完全错乱,业务上出现“订单号变成了用户ID”的诡异数据。

至于合并后的结果集列名,规则也很明确:以第一个 SELECT 里的列名或别名为准。也就是说,第二个 SELECT 哪怕是写出了别名,也不会出现在最终结果里。你想给结果集的列起一个像样的名字,直接改第一个 SELECT 的别名就行。

2.2 数据类型不匹配时,数据库会怎么做

列数对齐了,下一个问题就是数据类型。UNION 要求对应列的数据类型是兼容的。什么叫兼容?数字可以和数字合并,字符可以和字符合并,日期可以跟日期合并,这些都不难理解。难的是那些看似能合并,实际上有隐性转换的情况。

比如第一个 SELECT 查的是整数类型的 order_id,第二个 SELECT 查的是字符类型的 order_no,两边长度、格式可能都不太一样。数据库通常会根据所有分支的列类型,推断出一个兼容的类型,然后对其中一边做隐式转换。在 MySQL 里,合并后的列类型会优先参考第一个 SELECT 的类型,但同时也会尽量选一个能容纳所有分支数据的类型。一个常见的结果是,INT 和 VARCHAR 合并之后,列类型变成了 VARCHAR,原本是数字的 order_id 也变成了字符串。

这种隐式转换带来的问题,最典型的就是索引失效。假如你在 WHERE 条件或 JOIN 条件里使用这个合并后的列去跟数字比较,数据库可能无法直接使用列上的索引,被迫做全表扫描,性能一下就崩了。所以我的习惯是:只要发现两个分支的列类型不完全一致,就主动用 CAST 函数把它们统一成同一个类型,哪怕多写几行代码也值。

另外,还要提醒一下 NULL 的问题。UNION 去重时,会把多个 NULL 当作同一个值处理,这个语义和 DISTINCT 保持一致。举个例子,两张表里各有一条记录的备注字段是 NULL,用 UNION 合并后会只剩一条,而用 UNION ALL 会保留两条。很多人写 SQL 时没注意这个细节,结果发现行数对不上账,排查半天才意识到是 NULL 被合并了。

2.3 ORDER BY 和 LIMIT 的位置有讲究

UNION 和 ORDER BY、LIMIT 的组合,是另一个高频翻车点。不少新手会想当然地认为,每个 SELECT 后面都可以跟自己的 ORDER BY 和 LIMIT,但实际上整个 UNION 语句只允许在最后面统一放一个 ORDER BY,这个 ORDER BY 是对合并后的最终结果集排序的,不能只对其中某一个分支排序。

比如下面这条 SQL 在大多数数据库里并不会像你想的那样工作:

SELECT order_id FROM orders_2023 ORDER BY created_at DESC UNION ALL SELECT order_id FROM orders_2024 ORDER BY created_at DESC;

这句话的本意可能是“分别取两年订单,各自按时间倒序”,但数据库并不会按你的意图去理解。它不仅可能报语法错误,就算能执行,这个 ORDER BY 也会被忽略或者被当成最终排序的一部分,结果完全不是你以为的那样。

正确的做法是:如果确实需要“先取每个分支的前 N 条,再合并”,就得用子查询把每个分支包起来。以 MySQL 为例,可以写成这样:

SELECT * FROM ( SELECT order_id, created_at FROM orders_2023 ORDER BY created_at DESC LIMIT 10 ) t1 UNION ALL SELECT * FROM ( SELECT order_id, created_at FROM orders_2024 ORDER BY created_at DESC LIMIT 10 ) t2;

这里有个细节需要特别注意:在 MySQL 8.0.19 之前,把带 LIMIT 的 SELECT 加上括号直接做 UNION,语法上会报错,必须像上面这样包一层子查询。8.0.19 之后才放宽了这个限制。如果是 SQL Server,可以直接在每个分支的 SELECT 里用 TOP,写成 SELECT TOP 10 order_id FROM orders_2023 UNION ALL SELECT TOP 10 order_id FROM orders_2024,这个语法更直观。

还有一个点:如果 UNION 语句末尾带了 ORDER BY,这个 ORDER BY 会作用于整个合并结果集。假如你要对合并后的结果做分页,比如只取前 20 条,直接在语句末尾加 LIMIT 20 是对的。但要注意,这和“每个表各取 20 条”是完全不同的两件事,业务方提需求时如果你不追问清楚,很容易在分页这里栽跟头。这个坑我后面会专门展开讲。

3. 一组实测数据与执行计划分析

3.1 测试环境与对比 SQL

光说不练假把式,我特意在一台普通配置的测试机上跑了一组对比。环境大概是这样的:MySQL 8.0,机器是普通的 SSD,内存 16G,两个表的数据量分别是 800 万行和 600 万行,两表之间有接近 200 万行的数据是重复的。表结构很简单,核心字段就几个:order_id、user_id、order_amount、created_at。

对比 SQL 就两句话,唯一区别是 UNION 和 UNION ALL:

-- 去重合并 SELECT order_id, user_id, order_amount, created_at FROM orders_2023 UNION SELECT order_id, user_id, order_amount, created_at FROM orders_archive; -- 不去重合并 SELECT order_id, user_id, order_amount, created_at FROM orders_2023 UNION ALL SELECT order_id, user_id, order_amount, created_at FROM orders_archive;

两个 SQL 返回的数据量差距很明显。UNION 返回约 1200 万行,UNION ALL 返回约 1400 万行,多出来的这 200 万行就是两表重复的那部分。但更让我在意的是耗时:UNION 用了 9.8 秒,UNION ALL 只用了 1.7 秒,差了将近 6 倍。

这里我要强调一下,具体数字会因为机器配置、数据分布、索引情况而有差异,别把这个 6 倍当成放之四海而皆准的结论。但趋势是确定的:去重操作带来的额外开销,会随着数据量的增长被放大得越来越明显。在数据量只有几百条的临时表上你可能完全感受不到差别,一旦上了生产环境的大表,这个差距就是线上事故和正常请求之间的区别。

3.2 执行计划里到底多了什么

如果你在 MySQL 里对上面的 UNION 查询做 EXPLAIN,会看到最终结果来自一个名为<union1,2>的派生表,Extra 列里大概率会写着 Using temporary 和 Using filesort。这两行英文翻译过来就是:数据库创建了一张临时表,并且对这张临时表做了排序。排序的目的自然是为了把相邻的重复行找出来并去掉。

我把关键部分贴出来给大家一个直观感受:

id select_type table type rows Extra 1 PRIMARY <union1,2> ALL NULL Using temporary; Using filesort 2 UNION orders_2023 ALL ... ... 3 UNION orders_archive ALL ... ...

也就是说,整个执行过程大概是:先分别把 orders_2023 和 orders_archive 的数据读出来,拼成一个结果集,然后再对结果集进行统一的排序和去重。如果排序数据量超过了 MySQL 的 sort_buffer_size,MySQL 会把中间结果分批写到磁盘上的临时文件里,这也是为什么数据量一大之后,UNION 的耗时会出现断崖式上升。

对比之下,UNION ALL 的执行计划里不会有 Using temporary 和 Using filesort,只有简单的表扫描和拼接。SQL Server 里也会看到同样的逻辑:UNION 对应的执行计划中有一个 Distinct Sort 操作符,UNION ALL 则只有 Concatenation。如果你想亲眼确认,可以试试在 SQL Server 里执行 SET STATISTICS TIME ON,然后分别跑两条同样的 SQL,看消息栏里输出的 CPU 时间和逻辑读次数,差异会非常直白。

3.3 UNION ALL 替代 UNION 的优化思路

既然 UNION 的去重开销这么大,一个自然的想法就是:能不能先用 UNION ALL,再在业务层或者外层查询里做去重?这个思路在很多场景下是可行的,而且效果相当好。

最典型的替代方案是 UNION ALL 加 GROUP BY。比如上面的例子,我们只需要按 order_id 去重,其他字段随便取一个,就可以这样写:

SELECT order_id, MAX(user_id) AS user_id, MAX(order_amount) AS order_amount FROM ( SELECT order_id, user_id, order_amount FROM orders_2023 UNION ALL SELECT order_id, user_id, order_amount FROM orders_archive ) t GROUP BY order_id;

这个写法的好处是,去重逻辑从“对所有列整体排序去重”变成了“按键分组聚合”。如果 order_id 上本身有索引,分组效率会比全量排序高不少;更重要的是,它给了你主动权,你可以选择哪些列参与去重,哪些列用聚合函数取一个代表值,这在业务上往往更贴合需求。

但我要提醒一句:这个方法不一定总是更快。如果两个表的数据本身几乎不重复,UNION 的排序开销其实也不会太大,反而 GROUP BY 还会多做一次聚合。所以在做这种优化前,最好先确认一下数据的重复率。你可以用一条 COUNT 对比 SQL 快速估算重复量,我一般会这样查:

SELECT COUNT(*) AS total_cnt, COUNT(DISTINCT order_id) AS distinct_cnt FROM ( SELECT order_id FROM orders_2023 UNION ALL SELECT order_id FROM orders_archive ) t;

如果 total_cnt 和 distinct_cnt 差距很小,说明重复数据很少,UNION 的浪费主要在于“为了去重而排全量序”。这时候用 UNION ALL 加 GROUP BY 或者干脆直接用 UNION,差别可能没那么大,优化空间也有限。反过来,如果重复率很高,GROUP BY 方案的收益就会非常可观。

还有一种情况是业务上明确要求保留“重复中的某一条”,比如两个来源的会员数据有重叠,需要按手机号合并,保留注册时间最早的记录。这种场景用 UNION 根本做不了,因为 UNION 只能整行去重,无法告诉你“重复时该信谁”。正确做法是用窗口函数,后面我会给具体示例。

4. 高频故障与排查清单

4.1 结果顺序不稳定怎么办

运行同一条 SQL,两次返回的结果顺序不一样,这是 UNION 使用中非常常见的问题。很多人第一次遇到的时候会怀疑是不是数据库出 bug 了,其实不是。

原因很简单:UNION 的去重逻辑依赖排序,而排序的结果受数据分布、内存大小、并发因素影响,除非你显式指定了 ORDER BY,否则数据库返回什么样的顺序都是合理范围。尤其当排序数据量超过了内存阈值,需要走磁盘临时文件时,顺序就更不稳定了。UNION ALL 虽然没有排序步骤,但它的输出顺序只代表“这次执行时数据库拼接的先后顺序”,同样不是数据库给你的契约。

所以记住一个铁律:任何依赖查询结果顺序的业务逻辑都是错误的。你需要排序,就在语句最后显式加 ORDER BY,别指望隐式顺序。这条原则适用于所有 SQL 查询,UNION 场景只是更容易暴露这个问题而已。

4.2 怎样快速定位“多出来的重复行”

用了 UNION ALL 之后发现结果集比预期多了不少行,这是另一种高频问题。要快速定位是不是有重复,最简单的方法是对比两种写法的行数:

-- 先看不去重的行数 SELECT COUNT(*) FROM (SELECT ... UNION ALL SELECT ...) t; -- 再看去重的行数 SELECT COUNT(*) FROM (SELECT ... UNION SELECT ...) t;

如果两个 COUNT 的值一样,说明两个结果集之间没有整行重复的数据,你可以放心用 UNION ALL。如果差值很大,说明确实存在大量的整行重复。这时候再进一步查一查,到底是哪些字段组合重复了,可以用 GROUP BY 加 HAVING:

SELECT order_id, COUNT(*) FROM ( SELECT order_id, user_id, order_amount, created_at FROM orders_2023 UNION ALL SELECT order_id, user_id, order_amount, created_at FROM orders_archive ) t GROUP BY order_id HAVING COUNT(*) > 1;

如果查出来重复行很多,先别急着去重,而是要搞清楚业务上这些重复是否应该存在。比如两个表存的是不同年份的订单,order_id 理论上不会重复,如果查出来重复,那很可能是数据本身有问题,比如历史数据被重复导入了。只凭 UNION 去重,等于把这个数据质量问题掩盖了。这一点我觉得比性能还重要,因为数据库不会帮你判断数据对错,它只会按你写的逻辑执行。

4.3 分页取数时最容易写错的场景

分页和 UNION 的组合堪称经典翻车现场。我遇到过好几次这样的需求:要从 A 表和 B 表各取最新的 10 条记录,合并后返回给前端。新手最常见的写法是这样:

SELECT ... FROM table_a UNION ALL SELECT ... FROM table_b ORDER BY created_at DESC LIMIT 10;

这个写法的意思是:先把两表数据全部合并,再按时间倒序排序,最后只取前 10 条。如果 A 表最新数据的时间整体比 B 表新,很可能这 10 条全部来自 A 表,B 表一条都没有。这和“各取 10 条再合并”的需求差了十万八千里。

正确的写法在前面已经提过,需要把“取前 10 条”的动作放在每个分支内部完成,再对合并后的结果排序:

SELECT * FROM ( SELECT order_id, created_at FROM table_a ORDER BY created_at DESC LIMIT 10 ) t1 UNION ALL SELECT * FROM ( SELECT order_id, created_at FROM table_b ORDER BY created_at DESC LIMIT 10 ) t2 ORDER BY created_at DESC;

在 SQL Server 里对应写法是每个分支用 TOP,语法更紧凑。在 Oracle 里则是 FETCH FIRST 10 ROWS ONLY。

这个问题的本质是:先聚合再筛选,还是先筛选再聚合。很多人一上来就写 UNION,完全没想过筛选顺序会彻底改变结果。建议大家在写这类 SQL 前,先把需求里的“每张表”和“合计”这两个关键词抓出来,分清到底是全局 TOP N 还是分组 TOP N。

4.4 不同数据库的行为差异备忘

UNION 和 UNION ALL 是标准 SQL 语法,主流数据库都支持,但在细节行为上还是有不少差异。我把一些我踩过或看过的差异整理成了一张表,供参考:

数据库去重实现常见注意点
MySQL排序/哈希去重,大结果集会出现 Using temporary; Using filesort8.0.19 之前括号 LIMIT 语法受限;结果列类型按兼容性推断
SQL ServerDistinct Sort 操作符,内存不足会写 tempdb分支内可用 TOP;类型不匹配可能报错或隐式转换
PostgreSQL排序去重为主,对类型要求更严格对应列的排序规则不一致时容易报错;UNION 结果不支持直接当子查询的某些旧版本写法
Oracle同样会做排序去重,UNION ALL 只拼接类型不匹配时可能报 ORA-01790;NULL 排序规则与国际排序设置有关

这里提醒一点:如果你负责的系统需要同时兼容多种数据库,SQL 里尽量不要依赖某个数据库特有的语法。比如分支内的 TOP 就是 SQL Server 专用写法,MySQL 用的是 LIMIT,Oracle 最早用 ROWNUM,后来才有 FETCH FIRST。为了可移植性,最好统一用子查询加标准 SQL 的方式实现。

5. 实战经验:我在项目中用它踩过的坑

5.1 先问业务,再谈性能

有一次我做数据对账,需要从两个系统里各导出一份订单明细,然后找出两边的差异。当时图省事,直接在 SQL 里写了 UNION,让数据库帮我去重。结果跑出来之后,两边的总金额对不上,排查了整整一个下午,最后才发现问题出在一个非常隐蔽的地方:两个系统的订单号规则不一样,A 系统的订单号是纯数字,B 系统的订单号带字母前缀。UNION 时数据库做了隐式类型转换,把 B 系统的订单号截断或者转成了奇怪的东西,导致大量本来不同的记录被当成了重复项,直接被去重去掉了。

从那以后我养成了一个习惯:写 UNION 之前,一定要先弄清楚字段在两个系统里的真实含义和格式,别假设同名同类型就是同一个东西。这听起来像是常识,但在实际项目里,大家为了赶进度,真的很少有人会停下来确认这些细节。等测试数据一切正常上线后,才发现生产环境里全是坑。

业务语义永远排在性能前面。一个查询慢一点,还有优化的空间;一个查询结果错了,轻则返工,重则直接影响业务决策,这个代价不是几秒钟能补回来的。

5.2 只按部分列去重时,UNION 解决不了

业务上经常会有这种需求:合并两个来源的会员名单,同一个手机号只保留一条记录,优先保留注册时间最新的那条。这种“按部分列去重”的逻辑,UNION 是做不到的,因为 UNION 的“重复”是指整行所有列都相同,而你希望的是只看手机号这一个维度。

这种场景下,我通常会用 UNION ALL 把数据先合并,再借助窗口函数打标,然后过滤。以 SQL Server 为例,大致是这样的写法:

WITH combined AS ( SELECT member_id, mobile, register_time, source FROM member_2023 UNION ALL SELECT member_id, mobile, register_time, source FROM member_archive ), ranked AS ( SELECT member_id, mobile, register_time, source, ROW_NUMBER() OVER (PARTITION BY mobile ORDER BY register_time DESC) AS rn FROM combined ) SELECT member_id, mobile, register_time, source FROM ranked WHERE rn = 1;

这里用 ROW_NUMBER 按照手机号分组,按注册时间倒序编号,然后取每组第一行。好处是逻辑透明:你可以清楚地告诉业务方,“同一个手机号,我保留的是注册时间最新的记录”。如果以后规则变了,想保留来源优先级更高的记录,只需要改一下 ORDER BY 里面的字段,非常灵活。

MySQL 8.0 及之后也支持窗口函数,可以直接照搬。MySQL 5.7 这种老版本没有窗口函数,那就需要用 GROUP BY 加子查询来模拟,或者用变量实现。实现方式上会复杂一些,但核心思路是一样的:先把数据完整合并,再按业务键取一条。

5.3 NULL、空串和排序规则:隐藏的边界

最后说一个容易被忽视的细节:NULL、空字符串和排序规则对去重结果的影响。

前面提过,UNION 去重时会把多个 NULL 当作同一个值。这在大多数情况下是符合预期的,但如果你有两张表,每张表里都有一些记录的某个字段是 NULL,而这个字段恰好参与了整行比较,那么这些记录可能会被错误地合并成一条。这种问题在数据质量比较差的系统里特别常见,因为你根本不知道哪条记录是“更完整”的那条,也确实没有任何规则可以判断。

空字符串和 NULL 又是两种完全不同的值。在 MySQL 里,空字符串和 NULL 不相等,所以去重时它们会保留两条。但要注意,这里的行为和排序规则有关。比如在 utf8_general_ci 这种大小写不敏感的排序规则下,字符串 'abc' 和 'ABC' 会被认为相等,UNION 会把它们合并成一条;如果换成 utf8_bin 这种二进制排序规则,那就完全不会合并。

这意味着什么?意味着同样的数据,换一个数据库实例,或者改一下表级别的 COLLATE,UNION 返回的行数都可能发生变化。我做数据迁移的时候遇到过这种“隐形差异”:源库跑 UNION 得到 100 行,导入目标库之后跑同样的 SQL 变成 105 行,一查才发现是两边的排序规则不一样,导致去重判断标准变了。

所以我最后的建议是:凡是涉及 UNION 去重的场景,上线前一定要有“行数对比”这一步。不是跑一遍看结果对不对就行,而是专门跑不带排序、不带去重的 COUNT,和带 UNION 去重后的 COUNT,两个数对不上时,一定要追查差异行到底长什么样。这个习惯帮我擋掉了好几个潜在的线上事故,也推荐大家把它写进自己的测试用例里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询