UNION必须依赖临时表实现去重,因其执行流程为创建临时表→插入首查结果→逐行比对次查结果并写入→最终读取返回;而UNION ALL直接流式拼接,不建表、不比对、无锁。

UNION 必须去重,MySQL 只能靠临时表实现 —— 这不是 bug,是语义决定的。
UNION 的执行流程天然依赖临时表
MySQL 执行 UNION 时,并不会把两个结果集简单拼起来再筛重;它走的是「逐行插入 + 全量比对」路径:
- 先创建内存临时表(
CREATE TEMPORARY TABLE),结构由第一个子查询列定义 - 执行第一个子查询,所有行直接 INSERT 到该临时表
- 执行第二个子查询,对每一行都遍历临时表所有已有行做全量比对(
DISTINCT检查)——这一步必须读写临时表 - 最后从临时表 SELECT 出结果并销毁表
只要涉及写入和反复读取,InnoDB 就会对这个临时表加内部排他锁(LOCK_X),且事务未结束前不释放。哪怕两个子查询都是只读,UNION 自身仍强制触发写操作。
临时表落盘会让性能断崖式下跌
内存临时表一旦超出 tmp_table_size 和 max_heap_table_size 中较小值,立刻落盘为 MyISAM 临时表:
- 磁盘 I/O 替代内存访问,延迟跳升 10–100 倍
- 锁粒度从行级升级为表级,并发能力骤降
- EXPLAIN 中出现
Using temporary; Using filesort,说明已同时触发临时表 + 排序 - 含
TEXT/BLOB或宽列(如VARCHAR(500))也会强制落盘,无需数据量大
UNION ALL 为什么完全不走临时表
UNION ALL 不做任何去重,MySQL 直接流式输出:
- 第一个子查询执行完,结果边扫描边发给客户端
- 第二个子查询紧接执行,结果继续追加发送
- 全程无中间存储、无行间比对、无
INSERT或SELECT临时表动作 - EXPLAIN 中看不到
Using temporary,执行计划干净
注意:UNION ALL 和 UNION 对列类型、长度、NULL 处理的要求完全一致 —— 类型不兼容照样报错,不是“更宽松”。
哪些场景其实根本不需要 UNION
很多业务代码写着 UNION,但实际并不需要去重,只是惯性使然:
- 子查询通过
WHERE type IN (1,2)或主键分片(如id BETWEEN 1 AND 1000)天然无交集 → 可安全换UNION ALL - 外层已套
SELECT DISTINCT或应用层做了去重 → 内层UNION属冗余操作,应删掉 - 子查询含
GROUP BY或聚合(如COUNT(*)),结果本身唯一 →UNION的去重毫无意义 - 真需要去重,优先改写为
SELECT DISTINCT * FROM (subquery1 UNION ALL subquery2) AS t,把去重下推到最终一步,避免中间膨胀
真正难处理的,是那些看似需要去重、实则靠业务逻辑保证不重复,又没留日志验证的旧查询 —— 它们最容易被当成“安全替换”的盲区。


















