临时表本身不必然触发重编译,但其使用方式易触发明式重编译条件:DDL操作、统计信息更新超阈值(RT=500+0.2n)、跨作用域访问失败均会导致计划失效;应避免运行时建索引、改用SELECT INTO或表值参数,并慎用OPTION(KEEP PLAN)等提示。

临时表本身不会必然触发重编译,但它的使用方式极易踩中 SQL Server 的隐式重编译条件。 关键不是“用了#t”,而是“怎么用”——特别是涉及 DDL、统计信息变动、跨作用域访问时,优化器会主动放弃缓存计划。
CREATE INDEX 或 ALTER TABLE #t 会立即触发重编译
SQL Server 把本地临时表的 DDL 操作(如 CREATE CLUSTERED INDEX、ALTER TABLE #t ADD CONSTRAINT)视为“架构变更”,哪怕只在过程内部执行。一旦发生,当前执行计划立刻失效,后续所有调用都会走完整编译流程。
- 常见错误写法:
CREATE TABLE #tmp (...); INSERT INTO #tmp ...; CREATE INDEX IX_1 ON #tmp (col); SELECT * FROM #tmp - 正确做法:避免在运行时建索引;若必须加速查询,优先用
SELECT INTO #tmp隐式带聚簇(如SELECT col INTO #tmp FROM ... ORDER BY col),或改用表值参数(@tvp)+ 预定义主键 - 注意:
##global_tmp同样适用该规则,且并发下更危险——不同会话可能互相干扰索引定义
临时表数据量超过统计阈值后自动更新统计信息
SQL Server 对临时表的统计信息更新阈值比普通表更低,且公式固定:RT = 500 + 0.20 * n(当 n > 500)。只要插入/删除行数超过该阈值,就会触发 StatisticsChanged 类型的重编译,即使你没动过真实表结构。
- 典型现象:执行
sys.dm_exec_query_stats查该过程,发现plan_generation_num远大于execution_count - 验证方法:在过程开头加
DBCC SHOW_STATISTICS('#tmp', '_WA_Sys_...')(需先查出统计名),观察Rows和Rows Modified - 缓解方案:
OPTION (KEEP PLAN)可将临时表阈值拉高至普通表水平;OPTION (KEEPFIXED PLAN)则彻底禁用统计驱动的重编译(仅保留架构变更触发)
临时表跨过程传递失败导致逻辑被迫重构
本地临时表(#t)作用域严格限制在定义它的存储过程中,子过程无法访问。很多开发者试图“绕过”这点,结果写出易错模式:
- 错误模式:主过程建
#tmp→ 调用子过程 → 子过程里IF OBJECT_ID('tempdb..#tmp') IS NOT NULL SELECT * FROM #tmp(实际永远为 NULL) - 后果:子过程只能重新建表、再 INSERT,等于重复执行 DDL + DML,每次调用都新增一次重编译点
- 替代方案:用表值参数(
@tvp)传入中间结果;或用只读全局临时表(##tmp_readonly),但必须显式DROP TABLE ##tmp_readonly且加命名冲突防护
真正难排查的是“看似没改任何东西,却每次重编译”——往往卡在 nvarchar(max) 参数参与字符串拼接、或 SET ARITHABORT 连接级开关不一致这类隐蔽点。临时表只是最常被误用的载体,根源始终是 SQL Server 对“计划稳定性”的保守判断:它宁可多花 CPU 重编译,也不愿用一个它认为可能已不可信的计划。

















