直接查 sys.dm_exec_procedure_stats 只能获取当前缓存中的存储过程统计信息,已淘汰的计划无法显示;plan_handle 失效会导致关联 sys.dm_exec_query_plan 返回 null;推荐启用查询存储(sql server 2016+)以持久化保存历史执行计划与运行时指标。

直接看 sys.dm_exec_procedure_stats 就能知道存储过程是否被缓存、用了多久、执行了多少次——但要注意它只显示“当前还在缓存里”的过程,刚执行完就被踢出去的不会留下记录。
查不到执行计划?先确认 plan_handle 是否还有效
用 sys.dm_exec_procedure_stats 关联 sys.dm_exec_query_plan 时,query_plan 字段为 NULL 是最常见现象。这不是权限或语法问题,而是因为:
- 该存储过程的执行计划已被 SQL Server 从缓存中移除(比如内存压力大、被
DBCC FREEPROCCACHE清掉、或自动老化淘汰) - 过程里包含不缓存的语句(如
SELECT INTO #tmp、BULK INSERT、显式WITH RECOMPILE) - 查询时
plan_handle已失效,但统计行仍保留在dm_exec_procedure_stats中(统计信息比计划存活时间长)
想看“最近执行过但可能已下线”的计划?换用查询存储(Query Store)
如果实例是 SQL Server 2016+ 且启用了查询存储(默认关闭),它会持久化保存历史执行计划和运行时指标,不受内存缓存生命周期影响:
- 启用:
ALTER DATABASE [YourDB] SET QUERY_STORE = ON - 查某存储过程的历史计划:
SELECT * FROM sys.query_store_plan p JOIN sys.query_store_runtime_stats r ON p.plan_id = r.plan_id JOIN sys.query_store_query q ON p.query_id = q.query_id WHERE q.object_id = OBJECT_ID('YourProcName') - 注意:查询存储本身也占内存和磁盘,
MAX_STORAGE_SIZE_MB和清理策略要配好,否则可能停写
缓存占用量太大?别急着 DBCC FREEPROCCACHE
看到 sys.dm_os_memory_clerks 里 MEMORYCLERK_SQLPLAN 占用高,不代表必须清:
- 计划缓存本身是性能优化机制,清了会导致后续执行全部重编译,CPU 瞬间飙升
- 优先查哪些过程占得多:
SELECT TOP 10 object_name(object_id) AS proc_name, count(*) AS plan_count FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) t WHERE t.objectid IS NOT NULL GROUP BY object_name(object_id) ORDER BY plan_count DESC - 真要释放,也建议按对象粒度操作:
DBCC FREEPROCCACHE (0x05...)(传具体 plan_handle)比全清更安全
真正容易被忽略的是:计划缓存膨胀往往不是单个存储过程的问题,而是大量即席查询(Ad-hoc)或参数化不一致导致的“伪重复计划”。这类计划在 sys.dm_exec_cached_plans 里 type = 'Adhoc',且 usecounts = 1 —— 它们占内存却不复用,才是该优先识别和治理的对象。











