MySQL 5.7 中 UNION ALL 本身不强制创建临时表,但 EXPLAIN 显示 Using temporary 是因外层 ORDER BY/GROUP BY、子查询内 DISTINCT/GROUP BY/窗口函数、或 tmp_table_size 过小导致落盘,与 UNION ALL 无关。

UNION ALL 在 MySQL 5.7 中默认不创建临时表,但你仍可能看到 Using temporary,问题往往出在 ORDER BY、GROUP BY 或子查询本身,而不是 UNION ALL 本身。
为什么 EXPLAIN 里还有 Using temporary?不是说 5.7 不建临时表了吗
MySQL 5.7 确实取消了 UNION ALL 的强制临时表路径(WL#1763 已落地),但 Using temporary 仍可能出现,原因包括:
- 外层写了
ORDER BY或LIMIT:即使只加在最后,MySQL 仍需把全部结果暂存后排序——这不是 UNION ALL 的锅,而是排序行为本身触发的 - 某个子查询内部用了
GROUP BY、DISTINCT或窗口函数:这些会各自物化,和 UNION ALL 无关 - 合并后做了
GROUP BY(比如用UNION ALL + GROUP BY替代UNION):这时临时表是 GROUP BY 引起的,不是 UNION ALL -
tmp_table_size或max_heap_table_size太小,导致本该内存完成的排序/分组被迫落盘,EXPLAIN 就会标Using temporary; Using filesort
列对齐不严,会悄悄拖慢性能甚至隐式转类型
UNION ALL 对结构一致性要求极严,但报错只是表象,隐式类型转换才是性能隐患:
-
VARCHAR(10)和VARCHAR(255)合并时,MySQL 会按 255 宽度分配内存,后续若用于GROUP BY或JOIN,可能因长度超限导致索引失效 -
TINYINT和INT虽可兼容,但若字段参与排序,隐式升为INT会增大比较开销 - 混用
TEXT和VARCHAR可能触发截断警告,且TEXT列无法被索引用于排序,外层ORDER BY必然走 filesort - 务必显式用
CAST(col AS CHAR)或CONVERT(col, CHAR)统一文本类型,别依赖自动转换
大数据量下 ORDER BY 和 LIMIT 怎么写才不崩
外层 ORDER BY 是性能杀手,尤其当合并结果超百万行时:
- 错误写法:
(SELECT id FROM t1) UNION ALL (SELECT id FROM t2) ORDER BY id LIMIT 100→ 全量合并再排序,内存/磁盘压力大 - 更优解:每个子查询先
LIMIT,再合并,最后外层再LIMIT(适用于“取 Top N”场景):SELECT * FROM ( (SELECT id, name FROM order_2025_q4 ORDER BY created_at DESC LIMIT 200) UNION ALL (SELECT id, name FROM order_2026_q1 ORDER BY created_at DESC LIMIT 200) ) AS t ORDER BY created_at DESC LIMIT 100
- 如果业务允许近似有序,可考虑在各子查询中加时间范围过滤(如
WHERE created_at > '2026-01-01'),比全局排序便宜得多 - 确保
ORDER BY的列在每个子表上都有索引,否则子查询内的ORDER BY也会变成 filesort
子查询本身不优化,UNION ALL 再快也白搭
UNION ALL 只负责“拼”,不负责“查”。90% 的慢,其实卡在子查询里:
- 每个
SELECT都要单独EXPLAIN,确认是否走了索引、有没有Using temporary或Using filesort - 避免
SELECT *:只取必要字段,减少网络传输和内存占用;尤其别把TEXT、BLOB拉进来 - WHERE 条件必须下推:不要指望优化器自动把外层条件推到每个子查询,显式写进去,例如
SELECT id FROM t1 WHERE status = 1而不是SELECT id FROM t1再靠外层过滤 - 对分表场景(如按月订单表),确保每张子表的
WHERE条件能命中本地索引,而不是全表扫描
真正容易被忽略的是:UNION ALL 的“零开销”只存在于逻辑层面。一旦加上排序、分组、或子查询自身低效,临时表和 filesort 就会回来——它不背锅,但你得替它检查每一层。



















