MySQL 8.4 UNION ALL 为什么比 UNION 稳:去重临时表与排序开销边界
报表上线后,订单列表突然多了一层“合并历史数据”的查询。业务确认两张表不会存同一批订单,可 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,不是看起来少一个关键字,而是每个语义都能由数据和执行计划对上。
Go bytes.Buffer.Bytes 返回切片能保存多久:复用与数据快照的边界
- 上一篇
- Go bytes.Buffer.Bytes 返回切片能保存多久:复用与数据快照的边界
- 下一篇
- Go os.Root.OpenInRoot 如何阻止路径逃逸:相对路径与符号链接边界
-
- 数据库 · MySQL | 3小时前 |
- MySQL 8.4 自动生成隐式主键怎么识别:sql_generate_invisible_primary_key 与表结构验收
- 206浏览 收藏
-
- 数据库 · MySQL | 19小时前 |
- MySQL ONLY_FULL_GROUP_BY 下如何安全取组内任意值:ANY_VALUE 的适用边界与错误排查
- 371浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · SQL排查 · 聚合函数 · 数据库验证 · mysql group_concat group_concat_max_len 聚合字符串 结果截断
- MySQL GROUP_CONCAT 结果为什么被截断:group_concat_max_len 与字符数验收
- 353浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 5322次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4840次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4787次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 5040次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4990次使用
-
- MySQL 明明加了索引,为什么查询还是很慢?先查这 6 个点
- 2026-06-27 374浏览
-
- 接口返回的数据和数据库不一致怎么办?按数据生命周期排查
- 2026-06-27 398浏览
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- golang 基于 mysql 简单实现分布式读写锁
- 2023-01-07 384浏览
-
- 详解如何利用GORM实现MySQL事务
- 2023-01-07 184浏览
