MySQL 8.4 UNION ALL 为什么比 UNION 稳:去重临时表与排序开销边界
来源:17golang原创
时间:2026-08-27 19:58:30 395浏览 收藏
报表上线后,订单列表突然多了一层“合并历史数据”的查询。业务确认两张表不会存同一批订单,可 SQL 写成 UNION 后,慢日志里却出现了临时表和额外排序。这个场景里,真正要判断的不是“结果有没有重复”,而是数据库是否被要求为你做重复消除。
如果两段查询的结果允许重复,优先使用
UNION ALL;只有确实需要按整行去重时才用UNION,并用执行计划确认去重代价落在哪里。
UNION ALL直接追加两段结果,不主动做整行去重。UNION需要比较两段结果的完整行,可能引入去重临时表和排序。- 列数量、顺序和类型要先对齐,别把“业务唯一”误写成“SQL 自动唯一”。
- 验收要同时看结果行数、
EXPLAIN和临时表指标,不能只看一次耗时。
订单历史合并为什么会多出一段排序
准备两张结构一致的表:当前订单表 orders_current 保存近三个月数据,归档表 orders_archive 保存已归档数据。两段查询只取报表需要的四列:
SELECT order_id, user_id, status, created_at FROM orders_current WHERE user_id = 9001 UNION SELECT order_id, user_id, status, created_at FROM orders_archive WHERE user_id = 9001;
即使应用层已经按时间把两段表分开,UNION 仍按结果行判断重复。它不知道“当前表和归档表在业务上互斥”这条约定,必须先完成 duplicate removal,再把结果交给外层排序或分页。
这里先别急着给索引加列。第一步是确认合并操作本身是否多做了工作。
UNION ALL 和 UNION 的真实执行路径
把同一条件改成 UNION ALL,语义变成“按顺序追加两段结果”。它不替你证明订单唯一,也不替你消除重复;好处是查询可以更直接地把 orders_current 和 orders_archive 的结果送入后续处理。
而 UNION 会在合并阶段增加 duplicate removal。当结果集较大、列较宽,或外层还有 ORDER BY created_at 时,去重与排序都可能消耗内存并溢出到磁盘临时表。图中的节点是这条 SQL 实际会经过的判断链:

对照实验可以只替换一个词:
-- 两张表按业务边界互斥:保留重复行语义 SELECT order_id, user_id, status, created_at FROM orders_current WHERE user_id = 9001 UNION ALL SELECT order_id, user_id, status, created_at FROM orders_archive WHERE user_id = 9001; -- 需要整行去重时才使用 UNION SELECT order_id, user_id, status, created_at FROM orders_current WHERE user_id = 9001 UNION SELECT order_id, user_id, status, created_at FROM orders_archive WHERE user_id = 9001;
先用三项检查确认是否真的适合 UNION ALL
检查列定义,而不是只看列名
两段查询必须返回相同数量的列,位置对应的数据类型也要能安全合并。第一段的列名通常决定结果列名;如果一个分支把 created_at 转成字符,外层排序就可能出现隐式转换。
SELECT order_id, user_id, status, created_at FROM orders_current UNION ALL SELECT order_id, user_id, status, created_at FROM orders_archive;
检查“重复”是不是业务上可接受
如果归档动作是复制而不是迁移,同一个 order_id 可能同时出现在两张表。此时 UNION ALL 会忠实返回两行;它不是错误,也不会因为存在主键就替你去重。要保留哪一行,必须写出明确规则,例如按 created_at 取最新记录。
检查外层排序和分页
UNION ALL 只解决合并阶段的额外去重,不能保证整条 SQL 不排序。跨两张表统一按时间排序时,仍需要在最外层写 ORDER BY,分页还应补上稳定的第二排序键:
SELECT order_id, user_id, status, created_at FROM ( SELECT order_id, user_id, status, created_at FROM orders_current WHERE user_id = 9001 UNION ALL SELECT order_id, user_id, status, created_at FROM orders_archive WHERE user_id = 9001 ) AS merged_orders ORDER BY created_at DESC, order_id DESC LIMIT 50;
用 EXPLAIN 和结果行数做一次可复查验收
不要只比较客户端看到的耗时。对两个版本分别执行 EXPLAIN,记录每个分支的访问方式、估算行数和外层排序;再用计数查询核对结果是否符合业务预期。

EXPLAIN SELECT order_id, user_id, status, created_at FROM orders_current WHERE user_id = 9001 UNION ALL SELECT order_id, user_id, status, created_at FROM orders_archive WHERE user_id = 9001; SELECT COUNT(*) AS all_rows FROM ( SELECT order_id FROM orders_current WHERE user_id = 9001 UNION ALL SELECT order_id FROM orders_archive WHERE user_id = 9001 ) AS all_result; SELECT COUNT(*) AS distinct_rows FROM ( SELECT order_id FROM orders_current WHERE user_id = 9001 UNION SELECT order_id FROM orders_archive WHERE user_id = 9001 ) AS distinct_result;
如果 all_rows 明显大于 distinct_rows,说明两张表确实有重复键,不能只凭“归档表应该互斥”的设计文档切换。若两者长期相等,再结合归档约束和抽样数据,才有理由选择追加语义。
这几个坑会让 UNION ALL 的收益消失
- 把
UNION ALL当成去重版本:它不会删除重复行。 - 在每个子查询里分别写
ORDER BY,却没有配合LIMIT;最终顺序仍由外层排序决定。 - 用字符串拼接代替类型对齐,导致日期和数字在合并或排序时发生隐式转换。
- 只看一次冷缓存耗时,不记录结果行数、执行计划和临时表变化。
相关问题
UNION ALL 会不会比 UNION 永远快?
不会。它少了整行去重,但外层排序、宽字段、磁盘临时表或两段扫描仍可能成为瓶颈。
两张表没有重复主键,是否必须使用 UNION ALL?
不必须。应以数据库中实际可验证的约束和迁移流程为依据;没有稳定保证时,保留 UNION 的去重语义更安全。
只想按 order_id 去重怎么办?
UNION 的去重对象是整行,不是单列。需要按 order_id 选最新记录时,应使用窗口函数或聚合明确写出取舍规则。
把选择写成一条可执行规则
两张结果集业务上允许重复、列已对齐、外层排序也有明确成本时,使用 UNION ALL;必须整行去重时使用 UNION,并把 duplicate removal、sort、临时表和结果行数纳入验收。真正稳定的 SQL,不是看起来少一个关键字,而是每个语义都能由数据和执行计划对上。
-
374 收藏
-
398 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习