UNION 默认去重需临时表和排序/哈希,UNION ALL 直接追加结果不校验重复;EXPLAIN 中去重隐含在Extra字段或JSON格式的"union_result"里;ORDER BY/LIMIT须置于最外层;NULL多时UNION去重更慢,UNION ALL无影响。

UNION 和 UNION ALL 的执行计划差异
MySQL 优化器对 UNION 和 UNION ALL 的处理路径完全不同:前者默认去重,后者跳过去重。这意味着即使你写的是 UNION,只要没显式加 DISTINCT(其实 UNION 就是 UNION DISTINCT),优化器也必须走临时表 + 排序/哈希去重流程。
-
UNION会强制创建内部临时表,并在最后一步做去重(5.7+ 默认用哈希,8.0 更倾向使用排序) -
UNION ALL不生成临时表,各子查询结果直接追加,不校验重复行 - 如果子查询本身带
ORDER BY或LIMIT,UNION还可能触发额外的物化步骤(比如先各自排序再合并)
EXPLAIN 看不到去重操作?那是被隐藏了
执行 EXPLAIN 时,UNION 的去重逻辑不会单独显示为一行,而是体现在 Extra 字段里——常见值有 Using temporary、Using filesort,有时甚至只有 Using union(...) 却没提去重,容易误判。
- 查
EXPLAIN FORMAT=JSON才能看到完整流程:"union_result": {"using_temporary_table": true} - 若子查询含聚合或窗口函数,
UNION可能触发两层临时表(子查询一层,合并一层) -
UNION ALL的EXPLAIN通常只显示各子查询的独立执行计划,Extra干净,没有Using temporary
ORDER BY 和 LIMIT 必须放在最后,否则报错
MySQL 要求集合运算后的排序和分页必须写在最外层,不能分散在子查询里——这不是语法限制,而是优化器无法把子查询的 ORDER BY 提升到合并后生效。
- 错误写法:
(SELECT id FROM t1 ORDER BY id LIMIT 10) UNION (SELECT id FROM t2 ORDER BY id LIMIT 10)→ 实际执行顺序不保证全局有序 - 正确写法:
(SELECT id FROM t1) UNION ALL (SELECT id FROM t2) ORDER BY id LIMIT 10 - 如果真需要每个子集先排序再合并,得用派生表:
(SELECT * FROM (SELECT id FROM t1 ORDER BY id LIMIT 10) t1s) UNION ALL ...,但注意这会强制物化,性能更差
NULL 值比较会让 UNION 去重变慢
MySQL 判断两行是否重复时,对 NULL 使用三值逻辑(NULL = NULL 返回 UNKNOWN,但去重时视为相等)。这意味着含 NULL 的字段越多,哈希去重失败概率越高,优化器更容易回退到基于排序的去重方式,I/O 和 CPU 开销都会上升。
- 字段含大量
NULL时,UNION的临时表可能从内存溢出到磁盘(tmp_table_size不够) -
UNION ALL完全绕过该问题,不管有没有NULL,都不做任何比较 - 如果业务上确定无重复,别图省事写
UNION,尤其在大表关联或子查询返回宽列时,UNION ALL的性能优势可能达数倍
真正卡住性能的往往不是语法本身,而是去重逻辑在什么时机、用什么方式触发——特别是当子查询已经带了索引覆盖、却因为套了一层 UNION 被迫走临时表时,很容易被忽略。


















