union强制触发using temporary和using filesort,因其隐式等价于select distinct,需全量合并、排序去重,完全忽略子查询原有索引顺序;union all则无此开销,仅流式输出。

UNION会触发Using temporary和Using filesort
执行EXPLAIN时,只要看到Using temporary和Using filesort同时出现,基本就能确认当前UNION正在做全量去重+排序。这不是可选行为,是MySQL的强制流程:先拼接所有子查询结果(等价于UNION ALL),再进临时表、建哈希索引、逐行比对、最后排序输出。
而UNION ALL在EXPLAIN中不会出现这两个标记——它不建临时表,也不排序,只是把各子查询的结果流式写入网络缓冲区。
- 临时表一旦溢出内存(
tmp_table_size或max_heap_table_size不足),就会落盘到/tmp或innodb_temp_data_file_path,I/O延迟直接拉高耗时 -
Using filesort意味着原有索引顺序被破坏,后续如果接WHERE或JOIN,可能无法利用索引 - 即使你显式写了
ORDER BY,UNION仍会多一次隐式排序;而UNION ALL只执行你写的那一次
100万行数据下UNION比UNION ALL慢11倍
实测数据不是理论值:MySQL 8.0 + InnoDB + SSD环境下,100万行合并时,UNION ALL耗时约810ms,UNION高达9200ms。性能差距随数据量非线性扩大,50万行时已差近10倍。
瓶颈不在CPU,而在三处硬开销:
- 临时表写入SSD带来的随机I/O延迟
- 去重阶段的哈希计算与碰撞处理(尤其是含
NULL或长文本字段时) - 排序阶段的
O(n log n)比较成本,且无法并行
如果你的查询本身已按主键/时间戳有序,UNION的默认排序还会强行打乱这个顺序,白费前期优化。
哪些场景能安全换UNION ALL
判断依据只有一条:业务是否允许重复。不是“看起来没重复”,而是“设计上不可能重复”。
- 按时间分表的日志查询:
access_log_2023和access_log_2024天然无交集 - ID全局唯一的分库用户表:
user_shard1和user_shard2的user_id不重叠 - CTE中间结果拼接:
WITH a AS (...), b AS (...) SELECT * FROM a UNION ALL SELECT * FROM b,控制权在你手上
容易踩坑的是NULL值和隐式类型转换:NULL = NULL在UNION中算相等,但某些ORM或应用层逻辑可能忽略这点;TINYINT和INT列union时虽兼容,但值域差异可能导致你以为“没重复”实际有。
UNION和UNION ALL对列匹配的要求完全一致
别以为换UNION ALL就能绕过列约束——两者都要求:
- 子查询列数严格相同
- 对应列类型必须兼容(如
VARCHAR(255)和TEXT可隐式转换,但JSON和VARCHAR不行) - 报错信息一模一样:
ERROR 1222 (21000): The used SELECT statements have a different number of columns
真正省下的只有去重和排序开销。如果列不匹配,换UNION ALL只会让你更快拿到同一个错误。
最常被忽略的一点:UNION的“自动排序”不是按业务意义排,而是按SELECT列表第一列的字典序或数值序。如果你依赖这个顺序做分页或前端渲染,换成UNION ALL后必须显式加ORDER BY,否则结果顺序不可靠。











