t本身不会导致重编译,真正触发的是运行时建索引、统计信息超阈值更新(RT=500+0.2n,n≤6时阈值为6)、跨作用域访问失败三类操作;应避免显式DDL、改用SELECT INTO或表值参数,并慎用OPTION(KEEP PLAN)。

#t 本身不会导致重编译,真正触发重编译的是你对它的操作方式——运行时建索引、统计信息自动更新超阈值、跨作用域访问失败这三类行为最常踩坑。
CREATE INDEX 或 ALTER TABLE #t 立即触发重编译
SQL Server 把本地临时表上的 DDL 操作(如 CREATE CLUSTERED INDEX、ALTER TABLE #t ADD CONSTRAINT)视为“架构变更”,哪怕只在当前存储过程中执行,也会让整个执行计划立刻失效。
- 错误写法:
CREATE TABLE #tmp (id INT); INSERT INTO #tmp ...; CREATE INDEX IX_id ON #tmp (id);→ 必然触发一次重编译 - 正确做法:用
SELECT INTO #tmp隐式创建带聚簇的临时表(如SELECT id INTO #tmp FROM src ORDER BY id),避免显式 DDL - 若必须加速查询,优先改用表值参数(
@tvp)+ 预定义主键,而非运行时加索引 -
##global_tmp同样适用该规则,且并发下更危险——不同会话可能互相干扰索引定义
临时表统计信息更新阈值低,易触发 StatisticsChanged 重编译
临时表的统计更新阈值比普通表更低,且公式固定:RT = 500 + 0.20 * n(当 n > 500);n ≤ 6 时阈值恒为 6,这点极易被忽略。
- 典型现象:
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 )阈值是 <code>6,不是500,很多开发者在这里误判
子过程无法访问主过程的 #t,导致隐式重复 DDL
本地临时表(#t)作用域严格限制在定义它的存储过程中。子过程里用 IF OBJECT_ID('tempdb..#t') IS NOT NULL 判断永远为 NULL,结果只能重建表再插数据,等于新增一个重编译点。
- 错误模式:主过程建
#tmp→ 调用子过程 → 子过程里SELECT * FROM #tmp(报错或空结果)→ 不得不CREATE TABLE #tmp+INSERT - 替代方案:用表值参数(
@tvp)传入中间结果;或用只读全局临时表(##global_tmp),但要注意手动清理和并发风险 - 拆分逻辑时,按“数据生命周期”切分:把准备中间结果的逻辑抽成独立子过程,避免在主过程中反复建删
#local_tmp
重编译是否发生,不取决于你用了多少个 #t,而取决于你有没有在它身上做 DDL、有没有让它行数越过那个看似简单实则多变的统计阈值、有没有试图把它“传”给子过程——这三个动作,任何一个都可能让缓存计划当场作废。

















