union 默认去重并隐式排序,需用 union all 保留重复行且提升性能;列数、类型须严格一致,排序和 limit 只能作用于最终结果。

UNION 会自动去重,要保留重复行得用 UNION ALL
很多人写 UNION 是为了拼接两个结果集,但发现最终行数变少了——这是因为 UNION 默认执行去重(等价于 UNION DISTINCT)。如果两个查询里有相同结构的重复记录,它们会被合并成一条。
真正需要“叠加”数据时(比如日志表分月归档、多租户数据汇总),必须显式写成 UNION ALL。它不比较重复、不排序、不建临时表,性能也明显更好。
-
UNION:隐式加DISTINCT+ 隐式ORDER BY(按第一列升序),实际执行会建临时表并排序 -
UNION ALL:纯追加,零额外开销,适合大数据量拼接 - MySQL 8.0+ 对
UNION的去重逻辑更严格,遇到 NULL 或浮点精度差异也可能被判定为“不同”,但行为仍不如UNION ALL可控
列数、类型和顺序必须严格一致,否则报错 1222
执行 UNION 时 MySQL 不看字段名,只校验「列数」和「对应位置的表达式类型兼容性」。常见报错 ERROR 1222 (21S01): The used SELECT statements have a different number of columns 就是列数不匹配。
类型不兼容也会出问题:比如左边是 INT,右边是 VARCHAR,MySQL 会尝试隐式转换,但若转换失败(如 'abc' 转 INT)或精度丢失(如 DECIMAL(10,2) 和 DECIMAL(5,0) 拼接),可能返回截断值或警告。
- 所有
SELECT子句必须有相同数量的列,不能靠SELECT *蒙混——表结构稍有变动就崩 - 建议显式写出字段,用
CAST()或CONVERT()统一类型,例如:CAST(create_time AS DATE) - 列别名以第一个
SELECT的为准,后续子句的别名会被忽略
排序和限制只能加在最后,不能每个子句单独加 ORDER BY / LIMIT
UNION 是集合操作,整个结果是一个新结果集。你不能在每个 SELECT 后面加 ORDER BY 或 LIMIT,MySQL 会直接报语法错误(除非用括号包成派生表)。
请注意以下说明:1、本程序允许任何人免费使用。2、本程序采用PHP+MYSQL架构编写。并且经过ZEND加密,所以运行环境需要有ZEND引擎支持。3、需要售后服务的,请与本作者联系,联系方式见下方。4、本程序还可以与您的网站想整合,可以实现用户在线服务功能,可以让客户管理自己的信息,可以查询自己的订单状况。以及返点信息等相关客户利益的信息。这个功能可提高客户的向心度。安装方法:1、解压本系统,放在
如果真要对某一部分先取 top N 再合并,得用子查询包装:(SELECT ... ORDER BY x LIMIT 10) UNION ALL (SELECT ... ORDER BY y LIMIT 10)。但注意:子查询里的 ORDER BY 在没有 LIMIT 时无效,MySQL 5.7+ 会警告;有 LIMIT 才真正生效。
- 全局排序必须放在整个
UNION语句末尾,例如:... UNION ALL ... ORDER BY id DESC - 想按不同逻辑分别排序再合并?不行。得用应用层处理,或改用
JOIN+ 条件标记 -
LIMIT放在末尾只限制最终结果行数,不是每个分支的限制
性能差?检查是否误用了 UNION 而非 JOIN,或没加索引
UNION 本身不慢,慢往往是因为:① 用了 UNION 去替代本该用 JOIN 的关联场景;② 每个子查询都没走索引,导致全表扫描多次;③ UNION 后还要 ORDER BY + LIMIT,触发文件排序(Using filesort)甚至磁盘临时表。
用 EXPLAIN 看执行计划时,每个 SELECT 都会单独出现一行,type 为 ALL 就说明没走索引;Extra 出现 Using temporary; Using filesort 就是性能瓶颈信号。
- 两个表有主外键关系?优先考虑
LEFT JOIN,不是UNION - 子查询涉及大表,确保
WHERE条件字段上有索引,尤其注意函数索引失效问题(如YEAR(create_time) = 2024) - MySQL 8.0+ 支持 CTE,复杂拼接可先用
WITH定义中间结果,提升可读性和优化器判断能力
UNION 的边界很清晰:它只负责横向拼接,不关联、不过滤、不计算关系。一旦开始纠结“怎么让左边排好序再和右边拼”“为什么 LIMIT 不生效”,基本说明用错了场景。









