频繁重编译是缓存计划失效的信号,主因是统计信息过期、临时表滥用或参数嗅探失准;需先查plan_generation_num>1确认重编译,再验证参数值偏离、SET选项一致性及临时表使用规范,而非盲目加WITH RECOMPILE。

频繁重编译不是性能问题的表象,而是缓存计划失效的信号——它背后大概率是统计信息过期、临时表滥用或参数嗅探失准,而不是执行计划“老化”本身。
查清是不是真在重编译,而不是误判
别一慢就加 WITH RECOMPILE。先确认是否真的发生了多次编译:
- 运行
SELECT plan_generation_num, execution_count, last_execution_time FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st WHERE st.text LIKE '%你的过程名%',如果plan_generation_num> 1,说明已重编译过;但注意:这不等于该加WITH RECOMPILE - 用
SET STATISTICS XML ON执行一次,看执行计划里Parameter Compiled Value和你传的实际值是否严重偏离(比如编译时用了@status = 'Active',但这次传的是'Cancelled'且只占 0.02% 行) - 检查
sys.dm_exec_query_stats的last_elapsed_time波动是否同步于plan_generation_num上升——如果不是,慢可能来自锁、IO 或内存压力,而非编译
堵住最常见的重编译源头:临时表和 DDL 混用
在存储过程中建 #temp 表后立刻加索引、再查、再建另一个 #temp,这是最典型的自触发重编译链。SQL Server 把每次临时表结构变化都当成计划失效信号。
- 所有
#temp表必须在过程开头一次性定义完整(含主键、非聚集索引),后续不能再ALTER TABLE #t ADD或CREATE INDEX - 避免在同一个过程里反复创建/删除多个临时表(如
#t1、#t2、#t3),改用单个宽表 + 标志列区分逻辑分区 - 如果必须动态结构,优先用表变量
@t TABLE(...)(不触发重编译),但注意它没统计信息、大数据量时计划可能不准
参数嗅探失准时,优先用 OPTION (OPTIMIZE FOR) 而非 WITH RECOMPILE
WITH RECOMPILE 是全局重编译开关,代价高;而 OPTIMIZE FOR 只修正计划生成逻辑,保留缓存复用能力。
- 对高频调用且参数分布稳定的分支,显式指定典型值:
WHERE OrderDate >= @from OPTION (OPTIMIZE FOR (@from = '2025-01-01')) - 对极低频但逻辑特殊的参数(如
@mode = 'REBUILD_INDEX'),单独拆成另一个过程,避免污染主过程缓存 - 慎用
OPTIMIZE FOR UNKNOWN:它会让优化器忽略参数值、全按平均分布估算,对倾斜数据反而更差
别让 SET 选项变成隐形重编译触发器
同一过程被不同客户端调用时,SET ARITHABORT 开关不一致,会导致 SQL Server 认为这是两个不同会话环境,从而拒绝复用缓存计划。
- 在过程开头统一显式设置:
SET ARITHABORT ON(SQL Server 默认行为,但某些 ORM 或 SSMS 设置可能覆盖它) - 检查应用连接字符串是否含
ARITHABORT=false,如有,改为true或删掉(默认即 true) - 避免在过程内动态切换 SET 选项,尤其是
QUOTED_IDENTIFIER、ANSI_NULLS—— 它们在过程创建时即固化,运行中修改无效且可能报错
真正难处理的重编译,往往藏在“看起来没问题”的地方:比如某张基础表的统计信息 Modification Counter 已超阈值 5 倍却没自动更新,或者一个被忽略的 sp_recompile 调用残留了半年。查 sys.dm_db_stats_properties 和 sys.dm_exec_cached_plans 的 usecounts,比盲目加提示更可靠。

















