MySQL纵向拼接通过UNION/UNIONALL实现,核心区别在于UNION自动去重但性能差,应优先使用UNIONALL。需要去重时采用UNIONALL加外层DISTINCT。注意LIMIT和ORDERBY需用括号限定作用域,跨表OR条件可拆分为多条子查询用UNIONALL拼接以利用索引。
日常开发中,经常需要把多条独立SELECT查询的结果上下堆叠合并——也就是纵向拼接。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
很多人容易混淆两个概念:
JOIN:横向拼接,增加列;UNION / UNION ALL:纵向拼接,增加行。不少开发直接上手写UNION,遇上大数据量直接触发慢查询。与此同时,LIMIT失效、排序异常、索引无法利用、跨表OR改造这一系列踩坑点接踵而至,让人防不胜防。
这篇文章会系统梳理MySQL纵向拼接的语法、底层差异、规范写法,以及那些高频陷阱和线上最优实践。先说明一下,所有讨论都围绕真实的业务场景展开,不搞虚的。
用一个简单的例子来理解:
查询A结果:
id | name
1 | 张三
查询B结果:
id | name
2 | 李四
纵向拼接后:
id | name
1 | 张三
2 | 李四
这就好比是把两个班的学生名单上下叠在一起,而不是把学生信息左右拼接成一张表。
纵向拼接主要靠两个关键字:UNION和UNION ALL。
硬性规则(违反会直接报错)
SELECT *,字段结构变更会直接引发异常。基础示例:
-- 纵向拼接两条查询SELECT id, username FROM `user` WHERE status = 1UNION ALLSELECT id, access_key FROM `app_key` WHERE status = 1;
Using temporary; Using filesort;UNION = UNION ALL + DISTINCT 全局去重
举个重复数据的例子:
-- 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;
这样做的好处是:优化器可以自主选择哈希去重,不一定强制排序,优化空间更大,是线上标准写法。
先看一个错误写法:
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);
单独写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);
把全部拼接结果作为子查询,外层统一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;
原始问题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;
如果第一条分支命中,数据库就不需要继续执行第二条查询了,效率很高。
UNION ALL + DISTINCT;SELECT *,显式指定字段,保证结构稳定;DISTINCT。测试环境少量数据看不出差距;线上十万级结果集,临时表+排序会直接造成接口超时。这个坑,踩过的人都知道。
很多跨表OR、复杂多条件检索,拆分UNION ALL是唯一能稳定走索引的方案。
只有第一条SELECT的别名作为最终列名,后续子查询的别名会被忽略。这个细节很容易被忽略,但确实会导致意想不到的结果。
不会,重复记录会完整保留,必须手动处理。
使用EXPLAIN分析执行计划:
、Using temporary、Using filesort通过这个工具,能直观地看到两种写法的性能差异。
UNION / UNION ALL,作用是堆叠多行;横向合并依靠JOIN,二者不要混淆;LEFT JOIN + OR跨表条件导致的慢查询,首选方案:拆分为多条查询,UNION ALL纵向拼接;日常开发中要牢记:纵向拼接只是结果合并手段,无法提升单条查询的扫描效率,优化重心依然在每条分支SQL与索引设计上。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述