最稳妥识别无人使用的存储过程需组合三类数据源:sys.dm_exec_procedure_stats(查last_execution_time)、sys.dm_db_index_usage_stats(反向推表调用关系)、扩展事件或默认跟踪;同时须交叉验证显式引用、检查SQL Agent作业、NOEXEC测试及PowerShell扫描时避免截断与权限遗漏。

SQL Server 里怎么识别没人用的存储过程
直接查 sys.dm_exec_procedure_stats 最靠谱,但它只记录“重启后执行过”的过程——如果一个过程半年没跑过,但实例没重启过,它就压根不会出现在这个 DMV 里。所以不能只靠它。
更稳的做法是组合三类数据源:
-
sys.dm_exec_procedure_stats:看最近有没有执行痕迹(注意last_execution_time字段) -
sys.dm_db_index_usage_stats:虽然名字带“索引”,但它也记录了表级的user_seeks/user_scans,反向推哪些表被哪些过程调用过(需结合OBJECT_NAME解析) - SQL Server 默认跟踪或扩展事件(如
query_post_execution_showplan):适合长期监控,但默认不开启,得提前部署
别信 create_date 或 modify_date——改个注释就更新时间,毫无参考价值。
如何安全判断“无依赖”而不是“没调用”
一个存储过程没被调用,不代表能删;它可能是被应用代码硬编码调用、或通过 sp_executesql 动态拼出来的,根本不会出现在 sys.sql_modules 的 definition 文本搜索里。
真正要扫的是「显式引用」,用以下方式交叉验证:
- 查
sys.dm_exec_describe_first_result_set对每个过程执行一次元数据探测(不实际运行),看是否报错Invalid object name—— 这说明它依赖的对象已不存在,大概率是废弃的 - 用
sys.dm_exec_cached_plans+sys.dm_exec_sql_text扫缓存里的执行计划,过滤出含CREATE PROCEDURE或过程名的文本(注意大小写和 schema 前缀) - 在所有数据库的
sys.sql_modules中用CHARINDEX搜索'proc_name',但必须加QUOTENAME防止误匹配(比如搜usp_log结果把usp_login_history也抓进来了)
动态 SQL 是最大盲区。如果应用里有类似 EXEC('EXEC ' + @proc) 的写法,任何静态分析都失效。
自动化清理前必须做的三件事
删错一个过程可能让报表或定时任务静默失败,比报错还危险。上线前至少确认这三件事:
- 检查该过程是否在
msdb.dbo.sysschedules/syssqljobsteps里被 SQL Agent 作业调用(别只看 job 名字,要查command字段) - 导出它的完整定义(
OBJECT_DEFINITION(object_id)),用git diff或文本比对工具跟历史版本核对,确认近期没被改过逻辑 - 在非生产环境执行
SET NOEXEC ON+EXEC该过程,看是否报错Invalid column name或Invalid object name—— 这比语法检查更能暴露隐性依赖断裂
别跳过 NOEXEC 测试。有些过程里面嵌了 INSERT INTO #temp 再 SELECT,NOEXEC 能提前发现临时表结构不一致的问题。
PowerShell 脚本扫描时容易漏掉的点
用 Invoke-Sqlcmd 批量查 sys.procedures 很方便,但默认会截断 definition 字段到 4000 字符——这意味着你搜不到定义超长的过程里的关键字符串。
必须显式加参数:
-
-MaxCharLength 2147483647(PowerShell 5.1+) - 或者改用
System.Data.SqlClient手动设Command.CommandTimeout和SqlDataReader的GetString方法
另外,Invoke-Sqlcmd 在遇到权限不足的数据库时会静默跳过,不会报错。建议先用 SELECT name FROM sys.databases WHERE state = 0 拿到库列表,再逐个连接,捕获 $Error 输出。
依赖分析不是一次性活。表结构变、应用发版、甚至 SQL Server 补丁升级,都可能让某个“闲置”过程突然变成关键链路的一环。

















