union在mysql中默认强制使用临时表实现去重,而union all直接流式拼接结果、不建表不加锁;能否替换取决于业务是否真需去重,若无需则应优先用union all。

UNION 操作在 MySQL 中不是“必然”产生内部临时表,但绝大多数常见场景下确实会触发——关键在于它必须实现去重语义,而 MySQL 当前实现(截至 2026 年)把去重逻辑绑定在临时表结构上。
下面分几个实际开发中常遇到的问题点讲清楚:
UNION 去重依赖临时表主键约束
MySQL 实现 UNION 的方式是:先建一个内存临时表,字段与结果集对齐,并把首列(或所有列)设为唯一约束(类似 PRIMARY KEY 或 UNIQUE)。后续每个子查询的结果行都尝试 INSERT IGNORE 进该表——靠唯一性报错跳过重复行。这个机制天然需要可写、可查、带索引的中间存储,也就是临时表。
- 即使两个子查询都只读、无锁、走索引,
UNION自身仍要执行插入/校验动作 → 必然触发Using temporary -
EXPLAIN输出里第三行(或最后一步)出现Using temporary就是这个阶段 - 如果结果列含
TEXT/BLOB或总宽超tmp_table_size,会立刻落盘成 MyISAM 临时表,锁和 I/O 开销陡增
UNION ALL 为什么能绕过临时表
UNION ALL 不做任何行级比对,MySQL 直接将各子查询结果流式发给客户端。执行计划里看不到 Using temporary,也没有隐式 INSERT 动作。
- 每个子查询独立调度,InnoDB 可并行扫描(取决于隔离级别和 MVCC 状态)
- 不创建临时表 → 避免了
LOCK_X对临时表的持有,也杜绝落盘风险 - 注意:
UNION ALL本身不保证顺序,若需排序,ORDER BY必须显式写在最外层,且此时可能再次触发临时表(用于排序)
哪些情况看似用 UNION,其实根本不需要临时表
很多业务误以为必须用 UNION,但数据本身已天然无交集,去重纯属冗余。
- 子查询用互斥条件过滤:如
SELECT * FROM t WHERE status = 1UNIONSELECT * FROM t WHERE status = 2→ 可直接换UNION ALL - 子查询含
GROUP BY或主键SELECT id, COUNT(*) ... GROUP BY id→ 结果天然唯一,UNION的去重毫无意义 - 外层已加
DISTINCT或应用层做了 dedup → 内层UNION属于双重去重,应删掉或改UNION ALL
想保留去重但避开中间临时表膨胀?试试下推 DISTINCT
如果真需要去重,又担心 UNION 在中间阶段就建大临时表,可以把去重逻辑收束到最后一步:
SELECT DISTINCT * FROM ( SELECT id, name FROM t1 WHERE ... UNION ALL SELECT id, name FROM t2 WHERE ... ) AS t;
这样只有最终合并后的结果集参与去重,避免子查询结果反复写入/校验临时表。前提是子查询本身不带 ORDER BY / LIMIT / 聚合等会强制提前建临时表的操作。
真正容易被忽略的是:哪怕你把 UNION 换成 UNION ALL,只要子查询里有 GROUP BY 或 ORDER BY,依然可能各自触发临时表——UNION 本身只是其中一环,别让它背锅。











