查 sys.dm_exec_procedure_stats 常为空,因其仅保留当前缓存中的执行计划统计;计划被清除、重启、未执行或权限不足均导致无数据,需结合 DB_ID 过滤、ISNULL 补零并定时落库保存趋势。

直接查 sys.dm_exec_procedure_stats 能拿到执行次数,但结果常为空或明显偏低——这不是你写错了,而是这个视图本身有严格缓存前提和生命周期限制。
为什么查 sys.dm_exec_procedure_stats 经常返回空?
该视图只保留「当前缓存中仍存在执行计划」的存储过程统计。一旦计划被踢出缓存(比如内存压力大、DBCC FREEPROCCACHE、SQL Server 重启、AG 故障转移),对应记录就彻底消失,execution_count 归零且不再可查。
- 刚创建或修改过的存储过程,若尚未被执行过,不会出现在该视图中
- 执行过但计划已老化淘汰,
LEFT JOIN时会变成NULL,需用ISNULL(ps.execution_count, 0)补零 - 跨数据库查询时,
ps.database_id必须显式过滤,否则可能混入其他库的同名过程
sys.dm_exec_procedure_stats 和 sys.dm_exec_cached_plans 的关键区别
前者按过程对象维度聚合(每个 object_id 一行),含平均耗时、逻辑读等指标;后者按执行计划维度展示(同一过程多次编译可能多行),靠 p.usecounts 反映缓存命中频次,但不等于业务调用次数。
-
sys.dm_exec_procedure_stats.execution_count:自上次编译/启动后的实际执行次数(含所有上下文) -
sys.dm_exec_cached_plans.usecounts:该计划被复用的次数,不是调用次数;重编译后归零 - 若过程启用
WITH RECOMPILE,每次执行都生成新计划,usecounts永远是 1,但execution_count仍累加
必须加的权限和过滤条件
没 VIEW SERVER STATE 权限,查出来就是空结果集——连错误都不报。另外,不加 WHERE ps.database_id = DB_ID('your_db'),可能漏掉跨库调用或引入干扰项。
- 执行前确认:
SELECT HAS_PERMS_BY_NAME(NULL, NULL, 'VIEW SERVER STATE');返回 1 才有效 - 务必
JOIN sys.procedures或sys.objects补全过程名,因为sys.dm_exec_procedure_stats不存名称 - 避免
SELECT *,该视图字段多且部分为NULL,明确列出需要的列更安全
一个可靠的基础查询模板
下面这个语句能兼顾存在性、数据库范围和空值处理:
SELECT
DB_NAME(ps.database_id) AS database_name,
p.name AS procedure_name,
ISNULL(ps.execution_count, 0) AS execution_count,
ps.total_elapsed_time / NULLIF(ps.execution_count, 0) AS avg_duration_ms
FROM sys.procedures p
LEFT JOIN sys.dm_exec_procedure_stats ps
ON p.object_id = ps.object_id AND ps.database_id = DB_ID('YourDatabaseName')
WHERE p.is_ms_shipped = 0
ORDER BY execution_count DESC;
注意 NULLIF 防除零,is_ms_shipped = 0 排除系统过程;如果目标库名不确定,把 DB_ID('...') 换成变量或动态拼接。
真正难的是长期趋势——这个视图不持久,任何服务中断都会让历史归零。如果要监控周级变化,得定时跑这个查询并把结果存进自定义日志表,别指望它自己记住。

















