游乐游手机版
首页/数据库/文章详情

MySQL UNION与UNION ALL纵向拼接性能优化指南

时间:2026-07-24 19:39
MySQL纵向拼接通过UNION UNIONALL实现,核心区别在于UNION自动去重但性能差,应优先使用UNIONALL。需要去重时采用UNIONALL加外层DISTINCT。注意LIMIT和ORDERBY需用括号限定作用域,跨表OR条件可拆分为多条子查询用UNIONALL拼接以利用索引。

前言

在日常数据库开发中,经常需要将多条独立SELECT查询的结果集上下堆叠合并——也就是纵向拼接操作。

MySQL中多条查询结果纵向拼接(UNION/UNION ALL)优化指南

很多开发者容易混淆两个核心概念:

  • JOIN:横向拼接,用于增加列;
  • UNION / UNION ALL:纵向拼接,用于增加行。

不少开发人员习惯直接使用UNION,但遇到大数据量时很容易触发慢查询。与此同时,LIMIT失效、排序异常、索引无法利用、跨表OR改造等一系列踩坑点接连出现,让人防不胜防。

本文将系统梳理MySQL纵向拼接的语法规则、底层差异、规范写法,以及那些高频陷阱和线上最佳实践。需要说明的是,所有讨论都围绕真实业务场景展开,绝不空谈理论。

一、什么是纵向拼接

  • 横向拼接(JOIN):两张表根据关联字段左右合并,行数重组,字段增多
  • 纵向拼接(UNION系列):把多条查询结果上下堆叠,字段结构保持一致,行数累加

用一个简单例子来理解:

查询A结果:

id | name

1 | 张三

查询B结果:

id | name

2 | 李四

纵向拼接后:

id | name

1 | 张三

2 | 李四

这就好比把两个班级的学生名单上下叠在一起,而不是把学生信息左右拼接成一张表。

二、基础语法与强制约束

纵向拼接主要依赖两个关键字:UNIONUNION ALL

硬性规则(违反会直接报错)

  1. 每条子查询列数量必须完全一致
  2. 对应位置字段的数据类型尽量兼容;
  3. 最终字段名称由第一条SELECT决定,后续子查询的别名无效;
  4. 不推荐子查询使用SELECT *,字段结构变更会直接引发异常。

基础示例:

-- 纵向拼接两条查询SELECT id, username FROM `user` WHERE status = 1UNION ALLSELECT id, access_key FROM `app_key` WHERE status = 1;

三、UNION 和 UNION ALL的核心区别(重中之重)

UNION

  1. 合并结果后自动全局去重
  2. MySQL底层会创建临时表、执行排序比对重复;
  3. 执行计划大概率出现 Using temporary; Using filesort
  4. 性能较差,大数据量慎用。

UNION = UNION ALL + DISTINCT 全局去重

UNION ALL

  1. 直接原样纵向拼接,不去重、不排序
  2. 无临时表、无全局排序的开销;
  3. 性能远高于UNION,优先选用

直观对比测试

举个重复数据的例子:

-- UNION:自动剔除重复行SELECT user_id FROM `user` WHERE username = 'demo'UNIONSELECT user_id FROM `app_key` WHERE access_key = 'demo_key';-- UNION ALL:保留全部记录,包含重复SELECT user_id FROM `user` WHERE username = 'demo'UNION ALLSELECT user_id FROM `app_key` WHERE access_key = 'demo_key';

这背后的逻辑其实很简单:UNION多做的去重工作,是需要付出代价的。如果业务上允许重复,或者能通过其他方式处理重复,那UNION ALL就是最佳选择。

四、业务需要去重该怎么写?

不推荐:直接使用UNION。

推荐方案:UNION ALL + 外层 DISTINCT

SELECT DISTINCT user_id FROM (    SELECT user_id FROM `user` WHERE username = 'demo'    UNION ALL    SELECT user_id FROM `app_key` WHERE access_key = 'demo_key') t;

这样做的好处是:优化器可以自主选择哈希去重,不一定强制排序,优化空间更大,是线上标准写法。

五、高频踩坑:LIMIT 与 ORDER BY 的作用范围

陷阱1:不加括号,LIMIT只会作用在最后一条子查询上

先看一个错误写法:

SELECT id,username FROM `user` LIMIT 10UNION ALLSELECT id,access_key FROM `app_key` LIMIT 10;

MySQL会理解成:整体合并之后只取10行,而不是两条各自限制10条。

正确写法:子查询使用括号包裹

(SELECT id,username FROM `user` LIMIT 10)UNION ALL(SELECT id,access_key FROM `app_key` LIMIT 10);

陷阱2:子查询内的ORDER BY默认无效

单独写ORDER BY不会生效,只有搭配LIMIT时,括号内排序才会执行

-- 内部排序如果生效(SELECT id,username FROM `user` ORDER BY create_time DESC LIMIT 5)UNION ALL(SELECT id,access_key FROM `app_key` ORDER BY create_time DESC LIMIT 5);

陷阱3:想要整体结果统一排序

把全部拼接结果作为子查询,外层统一ORDER BY:

SELECT * FROM (    (SELECT id,username FROM `user` LIMIT 10)    UNION ALL    (SELECT id,access_key FROM `app_key` LIMIT 10)) tORDER BY id DESC;

六、经典业务场景:跨表OR条件优化(实战高频)

原始问题SQL(性能差,逻辑也存在隐患):

SELECT t1.id,t1.usernameFROM `user` t1LEFT JOIN `app_key` t2 ON t1.id = t2.user_idWHERE t1.username = 'demo' OR t2.access_key = 'demo_key';

这类LEFT JOIN + OR跨表条件极易索引失效。

标准优化手段:拆分查询,UNION ALL纵向拼接

-- 场景1:匹配用户表账号SELECT id, username FROM `user` WHERE username = 'demo'UNION ALL-- 场景2:匹配密钥表,关联查询用户SELECT t1.id, t1.usernameFROM `user` t1INNER JOIN `app_key` t2 ON t1.id = t2.user_idWHERE t2.access_key = 'demo_key';

如果还需要去重,外层包个DISTINCT。每条分支独立执行,能够正常使用各自索引,性能提升明显。

拓展:只需要查询任意一条匹配数据(短路查询)

比如登录、账号检索场景,找到第一条即可返回,减少扫描:

SELECT * FROM (    (SELECT id, username FROM `user` WHERE username = 'demo' LIMIT 1)    UNION ALL    (SELECT t1.id, t1.username FROM `user` t1     INNER JOIN `app_key` t2 ON t1.id = t2.user_id     WHERE t2.access_key = 'demo_key' LIMIT 1)) tmp LIMIT 1;

如果第一条分支命中,数据库就不需要继续执行第二条查询了,效率很高。

七、纵向拼接编码规范与优化建议

  1. 优先使用UNION ALL,杜绝无条件使用UNION;只有确认必须全局去重时,使用UNION ALL + DISTINCT
  2. 不要使用SELECT *,显式指定字段,保证结构稳定;
  3. 子查询需要限制行数,必须用括号包裹;
  4. 多条分支查询务必建立合适索引,纵向拼接不会提升单条子查询的性能;
  5. 分支数量不宜过多,过多子查询可读性会变差,可以考虑在应用层多次查询合并;
  6. 大数据场景避免上万行结果拼接,网络传输消耗较大;
  7. 不要依靠UNION实现单表内部去重,单表去重直接使用DISTINCT

八、常见误区汇总

误区1:UNION一定比UNION ALL简洁,少量数据无所谓

测试环境少量数据看不出差距;线上十万级结果集,临时表+排序会直接造成接口超时。这个坑,踩过的人都知道。

误区2:WHERE条件写在一起,不如UNION拼接灵活

很多跨表OR、复杂多条件检索,拆分UNION ALL是唯一能稳定走索引的方案。

误区3:子查询的字段别名全局生效

只有第一条SELECT的别名作为最终列名,后续子查询的别名会被忽略。这个细节很容易被忽略,但确实会导致意想不到的结果。

误区4:UNION ALL内部自动去重

不会,重复记录会完整保留,必须手动处理。

九、验证手段

使用EXPLAIN分析执行计划:

  • UNION:可见Using temporaryUsing filesort
  • UNION ALL:执行计划简洁,不存在全局临时表与排序

通过这个工具,能直观地看到两种写法的性能差异。

十、全文总结

  1. MySQL纵向拼接依靠UNION / UNION ALL,作用是堆叠多行;横向合并依靠JOIN,二者不要混淆;
  2. 性能铁律:优先UNION ALL;需要去重采用UNION ALL + DISTINCT,尽量避免直接使用UNION
  3. LIMIT、ORDER BY的作用范围容易踩坑,子查询增加括号控制作用域;
  4. LEFT JOIN + OR跨表条件导致的慢查询,首选方案:拆分为多条查询,UNION ALL纵向拼接;
  5. 任何优化的前提:每条独立子查询本身能够正常命中索引。

日常开发中要牢记:纵向拼接只是结果合并手段,无法提升单条查询的扫描效率,优化重心依然在每条分支SQL与索引设计上。

来源:https://www.jb51.net/database/367965bj3.htm
上一篇SQL索引工程设计最佳实践取舍逻辑场景适配落地规范 下一篇Hive Beeline能否进行数据恢复
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
自增主键值从何而来?深入理解原理,告别只会auto_increment
数据库 · 2026-07-25

自增主键值从何而来?深入理解原理,告别只会auto_increment

KingbaseES推荐使用serial、bigserial、显式sequence或identity列实现自增主键。serial创建integer并关联序列,bigserial对应bigint;显式sequence可自定义起始值等参数;identity有generatedbydefault(允许指定值)与always(禁止)两种模式。

Linux下瀚高数据库授权文件过期及替换解决方案
数据库 · 2026-07-25

Linux下瀚高数据库授权文件过期及替换解决方案

在银河麒麟系统下,瀚高数据库hgdb-4 5试用授权20天到期后需替换正式授权文件。正确操作:停止服务,备份旧文件,将授权文件复制到 opt highgo hgdb-4 5 etc lic 并命名为hgdb lic,设置权限600和属主highgo:highgo,再启动服务。禁止直接修改data目录下的license info文件。

Oracle BLOB实时同步的5大技术挑战与难点解析
数据库 · 2026-07-25

Oracle BLOB实时同步的5大技术挑战与难点解析

OracleBLOB实时同步面临分片组装、多列隔离、长事务跨窗口、事务回滚及大对象资源控制等技术挑战,必须在日志中精确还原完整字段值,才能保证源端与目标端数据完全一致,这对同步系统的稳健性提出了高要求。

MySQL禁用redo日志导致全备失败
数据库 · 2026-07-25

MySQL禁用redo日志导致全备失败

MySQL全量备份失败是由于数据定义语言操作触发排序索引构建,禁用重做日志导致XtraBackup无法获取一致性备份。测试验证表明,优化表语句即使无数据也会触发该问题。根本原因在于排序索引构建过程跳过了重做日志记录,破坏了备份的一致性。

Kafka架构图优化与改进的全面详细步骤与实践指南
数据库 · 2026-07-25

Kafka架构图优化与改进的全面详细步骤与实践指南

Kafka作为实时数据流处理的核心中间件,其底层架构虽已相当成熟,但在实际生产环境中,要充分发挥其性能潜力,仍需落实到具体的调优与架构改造上。核心目标可归纳为三点:如何承载更高的吞吐量、如何保障数据不丢失、以及故障发生时如何快速恢复。本文将从这几个关键方向出发,深入探讨如何真正榨干Kafka集群的性