临时表本身不触发重编译,但运行时建索引、统计信息自动更新、跨作用域访问失败三类操作会;CREATE INDEX或ALTER TABLE #t立即重编译;统计更新阈值为500+0.2n(n>500);可用OPTION (KEEP PLAN)或(KEEPFIXED PLAN)缓解。

临时表本身不会必然导致重编译,真正触发重编译的是你在过程里对它的操作方式——尤其是运行时建索引、统计信息自动更新、跨作用域访问失败这三类行为。
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 INTO #tmp隐式创建带聚簇的临时表(如SELECT col INTO #tmp FROM src ORDER BY col),避免显式 DDL - 若必须加速查询,优先改用表值参数(
@tvp)+ 预定义主键,而非运行时加索引 - 注意:
##global_tmp同样适用该规则,且并发下更危险——不同会话可能互相干扰索引定义
插入/删除行数超阈值后自动更新统计信息
临时表的统计信息更新阈值比普通表更低,公式固定: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 Modified - 缓解方案:
OPTION (KEEP PLAN)可将阈值拉高至普通表水平;OPTION (KEEPFIXED PLAN)则彻底禁用统计驱动的重编译(仅保留架构变更触发) - 小数据量场景(
n )阈值为 6,<code>6 时阈值恒为 500,这点容易被忽略
子过程无法访问主过程的 #t 导致隐式重复 DDL
本地临时表(#t)作用域严格限制在定义它的存储过程中。试图在子过程里用 IF OBJECT_ID('tempdb..#t') IS NOT NULL 判断并查询,结果永远为 NULL,最终只能重复建表+插入,等于新增一个重编译点。
- 错误模式:主过程建
#tmp→ 调用子过程 → 子过程里SELECT * FROM #tmp(实际报错或空结果)→ 不得不重建表再插数据 - 替代方案:用表值参数(
@tvp)传入中间结果;或用只读全局临时表(##global_tmp),但要注意手动清理和并发风险 - 拆分逻辑时,按“数据生命周期”切分:把准备中间结果的逻辑抽成独立子过程,输出明确、不共享未声明临时表
- 所有子过程必须显式
SET NOCOUNT ON,否则结果集计数会干扰缓存稳定性
最常被忽略的一点是:重编译是否真由临时表引起,得先看 sys.dm_exec_query_plan 中的 Reason 字段——SchemaChanged 和 StatisticsChanged 是关键线索;如果看到的是 Deferred Compile,那其实不是问题,而是 SQL Server 的正常延迟编译机制。

















