union all 性能显著优于 union,因其跳过去重开销,不建临时表、不排序、不比较;而 union 强制创建临时表并可能触发磁盘 i/o,导致性能断崖下跌。

UNION ALL 性能明显高于 UNION,尤其在大数据量时
直接结论:UNION ALL 几乎总是比 UNION 快,因为前者跳过了去重所需的全部开销。MySQL 不是“简单地多加一步去重”,而是整个执行路径完全不同。
关键差异在于:
-
UNION强制创建临时表(哪怕只有两行),底层用的是类似unordered_set的结构做重复判断,且默认会触发排序(Using filesort) -
UNION ALL是纯流式拼接:子查询各自执行完,结果逐块发给客户端,不建临时表、不排序、不比较 - 当
UNION的中间结果超出tmp_table_size或max_heap_table_size限制时,内存临时表会降级为磁盘临时表——I/O 暴涨,性能断崖下跌
去重操作如何把内存临时表拖进磁盘
很多人以为“只是去个重而已”,但 MySQL 的去重不是单次哈希查重,它依赖临时表的完整构建和扫描。一旦这个临时表撑爆内存,后果很实际:
- 内存临时表上限由
tmp_table_size和max_heap_table_size中较小值决定(单位字节) - 只要任意一个
UNION子查询返回的行体积 × 行数 > 该阈值,MySQL 就会把临时表转成磁盘表(MyISAM 引擎) -
EXPLAIN中出现Using temporary; Using filesort是双重警告:既用了临时表,又触发了外部排序 - 磁盘临时表不仅慢,还会争抢 I/O 资源,影响其他并发查询
什么时候必须用 UNION,而不是无脑上 UNION ALL
不是所有场景都能换。如果业务逻辑依赖去重,硬换 UNION ALL 会导致数据错乱,比如:
- 合并多个分表查询(如按时间分表的订单表),且业务要求“同一笔订单不能出现两次”
- 两个
SELECT查询可能覆盖相同主键范围(例如WHERE status IN (1,2)和WHERE status IN (2,3)),需要自然去重 - 下游系统或报表工具假定结果已去重,改用
UNION ALL后需同步修改消费方逻辑
注意:UNION 的去重是全字段比对,不是按主键;如果只希望按某几列去重,得用 GROUP BY 或窗口函数替代。
想提速但又不能去掉重复?试试这些折中方案
真要保留去重语义又想避开 UNION 的临时表陷阱,可以考虑:
- 提前在子查询里加
DISTINCT,缩小每个分支的结果集体积,降低临时表溢出概率 - 用
UNION ALL+ 外层GROUP BY替代(但注意GROUP BY同样可能触发临时表和 filesort) - 如果只是去重主键,用
SELECT ... FROM table1 WHERE id NOT IN (SELECT id FROM table2)拆开写,有时比UNION更可控 - 确认
tmp_table_size是否被设得太小(默认一般 16MB),适当调高可避免内存→磁盘切换,但别盲目堆大——内存资源有限
最常被忽略的一点:UNION 的性能黑洞不在“要不要去重”,而在“去重发生在哪一层”。只要子查询本身没索引、字段太宽、或结果集不可预估,临时表就大概率落地磁盘——这时候换写法比调参数更治本。











