当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 8.4 UNION ALL 为什么比 UNION 稳:去重临时表与排序开销边界

MySQL 8.4 UNION ALL 为什么比 UNION 稳:去重临时表与排序开销边界

来源:17golang原创 2026-08-27 19:58:30 0浏览 收藏

报表上线后,订单列表突然多了一层“合并历史数据”的查询。业务确认两张表不会存同一批订单,可 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_currentorders_archive 的结果送入后续处理。

UNION 会在合并阶段增加 duplicate removal。当结果集较大、列较宽,或外层还有 ORDER BY created_at 时,去重与排序都可能消耗内存并溢出到磁盘临时表。图中的节点是这条 SQL 实际会经过的判断链:

MySQL UNION ALL 从 orders_current 和 orders_archive 追加结果并跳过去重的查询路径

对照实验可以只替换一个词:

-- 两张表按业务边界互斥:保留重复行语义
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,记录每个分支的访问方式、估算行数和外层排序;再用计数查询核对结果是否符合业务预期。

MySQL UNION 增加 duplicate removal 和 sort 后的临时表资源路径,与 UNION ALL 的追加路径对照

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 removalsort、临时表和结果行数纳入验收。真正稳定的 SQL,不是看起来少一个关键字,而是每个语义都能由数据和执行计划对上。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go bytes.Buffer.Bytes 返回切片能保存多久:复用与数据快照的边界Go bytes.Buffer.Bytes 返回切片能保存多久:复用与数据快照的边界
上一篇
Go bytes.Buffer.Bytes 返回切片能保存多久:复用与数据快照的边界
Go os.Root.OpenInRoot 如何阻止路径逃逸:相对路径与符号链接边界
下一篇
Go os.Root.OpenInRoot 如何阻止路径逃逸:相对路径与符号链接边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    5322次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    4840次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    4787次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    5040次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    4990次使用