别再被UNION的“保险”诱惑了,性能差距比你想象的大得多 先丢个核心结论:UNION ALL 的性能往往比 UNION 高出不止一个数量级。原因很简单,UNION 在合并结果集之后会自动触发去重操作,这背后通常伴随隐式排序,进而产生临时表和文件排序。而 UNION ALL 则是将各个子查询的结果直
先丢个核心结论:UNION ALL 的性能往往比 UNION 高出不止一个数量级。原因很简单,UNION 在合并结果集之后会自动触发去重操作,这背后通常伴随隐式排序,进而产生临时表和文件排序。而 UNION ALL 则是将各个子查询的结果直接拼接,完全不碰数据内容。从执行计划上看,UNION 等价于 UNION ALL 再套一层 DISTINCT,这直接导致 Using temporary 和 Using filesort 的出现,I/O 和 CPU 开销自然就上去了。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
性能差距有多大?两个各返回 50 万行的子查询,用 UNION ALL 可能 200 毫秒内就流式返回,数据像流水一样从数据库里吐出。而 UNION 则会卡在 Using temporary; Using filesort 上,耗时数秒甚至直接内存溢出(OOM)都不奇怪。
如果你的查询出现以下情况,基本可以断定是 UNION 的去重逻辑在作祟:
Using temporary 或 Using filesorttmp_table_size / max_heap_table_size)被频繁打满这点非常隐蔽,但后果可能很严重。只要任意两行在所有列上完全相等,UNION 就会毫不留情地干掉一个。哪怕这两行来自不同的业务表,比如“正式员工”和“外包人员”里都叫“张三”、部门也相同,它也会被当作重复行过滤掉。这不是 bug,这就是 UNION 的设计行为。
更让人头疼的是顺序问题。UNION 的去重过程在 MySQL 8.0 以前尤其明显,会伴随隐式排序,导致最终结果的顺序完全不可控。而 UNION ALL 至少能忠实地保持各子查询的原始输出顺序——除非你显式加上 ORDER BY。
来看几个典型的应用场景,就知道什么时候该用哪个了:
log_20260501、log_20260502…)—— 数据天然不重复,直接用 UNION ALLUNION ALLUNION ALL 加默认分类)—— 明确要保留所有行,用 UNION ALL无论是 UNION 还是 UNION ALL,它们都不是“智能拼接”。它们只认位置,不认字段名。下面这些写法,MySQL 都会直接报错:
SELECT name, id FROM t1 UNION SELECT id, name FROM t2 —— 列顺序错乱,第一列拼的是 t1.name 和 t2.id,语义完全混乱SELECT created_at FROM orders UNION SELECT order_time FROM history —— 类型不兼容,比如 DATETIME 和 TIMESTAMP 在某些版本会直接报错SELECT x FROM a ORDER BY x LIMIT 10 UNION SELECT y FROM b ORDER BY y LIMIT 10 —— 语法非法,MySQL 会抛出 ERROR 1221正确的做法是什么?
CAST() 或 CONVERT(... USING utf8mb4) 显式转换数据类型SELECT id AS uid, name AS fullname FROM ...ORDER BY 只能放在整个查询的最后,并且只能引用列名或位置序号:... UNION ALL ... ORDER BY fullname(SELECT ... ORDER BY x LIMIT 10) UNION ALL (SELECT ... ORDER BY y LIMIT 10)只有当以下条件全部满足时,才值得考虑 UNION:
WHERE 或 JOIN 提前排重如果只是“怕有重复所以保险起见”,那反而容易埋下隐患。比如某天上游数据逻辑变更,导致本不该去重的行被意外合并,这种问题回溯起来非常困难。更稳妥的做法是:先用 UNION ALL 查出全量数据,再用 SELECT DISTINCT 包一层。虽然性能可能稍差,但语义清晰、可调试,出了问题也容易定位。
最后,还有一个很容易被忽略的细节:即使两个表结构一模一样,UNION 也会把 NULL 和 NULL 当作相等去重。但在很多业务场景里,NULL 的语义是“未知”,而不是“相同”。这一点在统计类查询中,很容易引发数据偏差,需要格外警惕。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述