UNION ALL根本不建去重临时表,因其语义即“不检查重复”,优化器直接走流式追加路径,跳过创建临时表、排序与哈希比对;而UNION等价于UNION DISTINCT,强制调用create_tmp_table()并嵌入Distinct节点。

UNION ALL 为什么根本不建去重临时表
因为 MySQL 优化器对 UNION ALL 的语义理解就是“不检查重复”,它压根不会触发去重逻辑,自然跳过创建临时表、排序、哈希比对这一整套流程。源码层面,UNION ALL 的执行路径是纯流式追加(Append),每个子查询结果直接推给客户端,中间不落地、不缓存、不校验。
而 UNION(等价于 UNION DISTINCT)在 st_select_lex_unit::prepare 阶段就会传入 distinct=true 参数,强制调用 create_tmp_table() 建临时表,并把去重作为必须步骤嵌入执行树末端的 Distinct 节点里。
EXPLAIN 里看不到 Using temporary 就说明没建临时表吗
对 UNION ALL 来说,基本可以这么判断——只要没写外层 ORDER BY 或 LIMIT,EXPLAIN 输出里就不会出现 Using temporary;但如果用了 FORMAT=JSON,还能看到更底层的确认项:"union_result": {"using_temporary_table": false}。
反例是:哪怕只加了 ORDER BY id 在 UNION ALL 外层,MySQL 就必须先把所有子查询结果合并进临时表再排序,此时 EXPLAIN 就会显示 Using temporary; Using filesort,和 UNION 的开销几乎一致。
-
UNION ALL+ 外层ORDER BY→ 必然建临时表 -
UNION ALL+ 每个子查询内ORDER BY→ 不建合并临时表,但各子查询可能各自物化 - 想避免临时表,就别在外层加排序或分页,或者改用应用层排序
字段类型不一致时,UNION ALL 报错但 UNION 不报?
不会。两者都要求列数、顺序、类型兼容,只是错误时机略有差异:UNION 会在准备阶段(prepare)就做类型统一和隐式转换检查;UNION ALL 同样在 prepare 阶段校验,一旦发现 VARCHAR(50) 和 TEXT 无法对齐,或列数不同,立刻报 ERROR 1222 (21000): The used SELECT statements have a different number of columns。
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
所谓“UNION 更宽容”是误解——它不是容忍类型差异,而是可能用更激进的隐式转换兜底(比如把数字转字符串),但这反而容易掩盖精度丢失或截断问题。真正安全的做法是显式 CAST 或 CONVERT 统一类型。
- 别依赖
SELECT *,尤其跨表或分表场景,列结构稍有变更就崩 - 时间字段用
CAST(created_at AS DATETIME)统一精度,避免DATETIME和TIMESTAMP混用 - 字符集不一致(如
utf8mb4vslatin1)也会导致类型冲突,需提前对齐
为什么有人换完 UNION ALL 还是被锁住
因为锁根本不在 UNION ALL 上——它不建临时表,不写中间数据,完全不持有内部排他锁(LOCK_X)。如果你换了之后还在 SHOW PROCESSLIST 里看到 Locked,问题一定出在子查询里:
- 子查询含
FOR UPDATE或LOCK IN SHARE MODE - 子查询扫描了未加索引的字段,触发了间隙锁(Gap Lock)
- 子查询访问的是同一张表的不同分区,InnoDB 对分区元数据加了锁
- 事务隔离级别是
REPEATABLE READ,且子查询读到了被其他事务修改但未提交的行
这时候查 INFORMATION_SCHEMA.INNODB_TRX 和 INNODB_LOCK_WAITS,才能定位到真实锁源。把 UNION 换成 UNION ALL 只是拆掉了“去重锁”,但没动任何一行业务数据的锁逻辑。










