缓存计划被踢出内存的常见原因包括:显式执行DBCC FREEPROCCACHE、内存不足时SQL Server自动回收低开销计划(usecounts=1且长期未调用)、统计信息自动更新导致计划过期、ALTER PROC或表结构变更使旧计划立即失效。

缓存计划被踢出内存的常见原因
执行计划不是永久驻留的,SQL Server 会根据内存压力动态清理过程缓存。最直接的表现是:某存储过程昨天还快,今天首次执行又变慢,sys.dm_exec_cached_plans里找不到对应 plan_handle了。
触发清理的典型场景包括:
-
DBCC FREEPROCCACHE或DBCC FLUSHPROCINDB被显式执行(运维或开发误操作) - 服务器内存不足,SQL Server 自动回收“低开销”计划——那些
usecounts = 1、长期未被调用、原始编译开销又小的计划优先被删 - 数据库自动更新统计信息(尤其
auto_update_statistics = ON),导致关联计划标记为“过期”,下次执行时强制重编译并生成新计划 - 执行了
ALTER PROC或修改了过程依赖的表结构(如加列、改类型),旧计划立即失效
如何确认计划是否被反复踢出再重编译
不能只看“第二次执行快”,得查底层状态。重点盯两个指标:
- 查
sys.dm_exec_query_stats中该过程的plan_generation_num:如果每次执行后该值都 +1,说明计划在持续重建 - 查
sys.dm_exec_cached_plans对应objtype = 'Proc'的记录,观察usecounts是否长期卡在 1;若始终为 1,基本等于没复用
更直接的方式是开启 SET STATISTICS XML ON 后执行过程,看返回的 XML 计划里是否有 <RelOp ... StatementOptmEarlyAbortReason="TimeOut"> 或 <QueryPlan ... CompileTime="xxx"> 值波动剧烈——首次高、后续低才说明缓存生效。
提高缓存重用率的实操要点
重用率低,往往不是缓存机制本身的问题,而是写法触发了隐式绕过。关键动作集中在“稳住文本一致性”和“切断参数干扰”:
- 禁用
EXEC(@sql):字符串拼接会让每次语句文本不同,彻底失去缓存资格;必须改用sp_executesql,且参数定义字符串(如N'@id INT')和实际值严格分离 - 统一
SET选项:在存储过程开头显式设置SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON;,否则客户端不同连接的默认值差异会导致计划不匹配 - 避免参数嗅探放大效应:对 WHERE 条件中选择性波动大的参数(如
@status在 0.1% 和 95% 分布间切换),不要直接用,改用局部变量承接:DECLARE @local_status TINYINT = @status;,后续查询只引用@local_status - 慎用
WITH RECOMPILE和语句级OPTION (RECOMPILE):它们等于主动放弃缓存,仅在极少数值分布完全不可预测的场景下权衡使用
容易被忽略的元数据影响
很多人查不到缓存,是因为没意识到对象解析依赖完整限定名。如果存储过程里写的是 SELECT * FROM Orders,而调用时用的是 EXEC dbo.usp_GetOrder,但当前会话的 DEFAULT_SCHEMA 不是 dbo,SQL Server 可能按不同 schema 解析表名,导致计划无法复用。
稳妥做法是:所有表、视图、函数引用都带 schema,例如 SELECT * FROM dbo.Orders;同时确保调用方也用完整名称 EXEC dbo.usp_GetOrder。这点在跨 schema 或有同名对象的库中尤为关键。

















