union执行计划必现using temporary和using filesort,因需临时表去重排序;union all则无此开销,各子查询独立执行。

存储过程里UNION和UNION ALL的执行计划差异怎么看?
在存储过程中,UNION 和 UNION ALL 的执行计划不会因为“写在存储过程里”就变特殊——数据库优化器仍按标准规则处理。关键看 EXPLAIN(MySQL)或 EXPLAIN ANALYZE(PostgreSQL)是否出现以下标记:
-
Using temporary和Using filesort:几乎必然出现在UNION中,说明触发了临时表 + 排序去重 -
UNION ALL对应的执行计划通常干净,各子查询独立走索引,无合并阶段开销
实操建议:
- 在存储过程内先用
SELECT单独跑两个版本,加EXPLAIN前缀对比 - 注意:MySQL 存储过程不支持直接
EXPLAIN CALL sp_name(),必须把核心查询抽出来单独分析 - PostgreSQL 可用
EXPLAIN (ANALYZE, BUFFERS)精确看到内存/磁盘使用差异
为什么存储过程里用UNION反而更容易掩盖性能问题?
存储过程封装了逻辑,容易让人忽略底层开销。典型现象包括:
- 存储过程返回结果慢,但调用者只看到“执行耗时 2.4s”,查不出瓶颈在哪
- 日志只记录“CALL my_proc()”,没记录内部 SQL 的
Using temporary - 多层嵌套(比如
UNION套在游标循环里)会让排序开销被放大数倍
实操建议:
- 在存储过程开头加
SELECT 'debug: union start';这类标记,配合慢日志定位具体语句 - 把含
UNION的查询拆成独立语句,在测试环境用真实数据量压测 - 避免在存储过程里对大结果集做
UNION后再ORDER BY—— 这等于双重排序
参数化查询下UNION ALL能否安全替代UNION?
不能靠“参数开关”动态切换操作符。SQL 语法要求 UNION 和 UNION ALL 是硬编码关键字,无法用变量替换。常见错误写法:
- 错误:
SET @op = 'UNION ALL'; SELECT ... @op SELECT ...→ 语法报错 - 错误:
IF need_dedupe THEN ... UNION ... ELSE ... UNION ALL ... END IF;→ 实际仍要写两套逻辑
实操建议:
- 真需要动态行为,用两个独立存储过程,比如
sp_merge_raw()(用UNION ALL)和sp_merge_deduped()(用UNION) - 或改用应用层控制:存储过程只返回原始数据,去重逻辑交给业务代码(尤其当重复行有业务含义时)
- 若必须统一入口,可用
IF分支拼接完整 SQL 字符串,再用PREPARE/EXECUTE执行(但注意 SQL 注入风险)
最容易被忽略的类型兼容陷阱
存储过程里字段类型隐式转换失败,UNION ALL 和 UNION 一样报错,不是“ALL 就更宽松”。典型场景:
- 第一个
SELECT返回NOT NULL VARCHAR(50),第二个返回NULL→ MySQL 8.0+ 直接报错 - 子查询中用了
CASE WHEN,分支返回类型不一致(如INT和VARCHAR)→ 两者都失败 - 列别名冲突:
SELECT id FROM t1 UNION ALL SELECT user_id AS id FROM t2没问题;但若第一个是SELECT id AS uid,结果列名永远是uid,后续应用可能取不到id
实操建议:
- 所有子查询显式 cast 类型:
CAST(col AS CHAR)或CONVERT(col, CHAR) - 用
SHOW CREATE PROCEDURE proc_name检查实际生成的 SQL 是否符合预期 - 在存储过程开头加
SELECT 1 FROM DUAL WHERE 0=1;占位,避免因语法错误导致整个过程创建失败却无提示
真正麻烦的不是选哪个操作符,而是很多人把存储过程当黑盒,直到线上慢查询报警才去翻执行计划——而那时 UNION 已经在百万级数据上跑了半年。











