如何捕获存储过程中执行的Update/Delete语句及受影响行数?
好问题!你想要捕获存储过程里每条Update/Delete的执行情况和受影响行数,这个需求很常见,但直接用你写的那个函数可能会遇到一些坑——比如sys.dm_exec_requests只能返回当前正在执行的请求,等你在函数里调用它时,存储过程里的DML语句可能已经执行完毕,根本不在当前请求列表里了。我来给你分享几个更靠谱的实现方案:
方案1:在存储过程内直接记录(最直接可控)
既然你已经知道存储过程里只有Update和Delete,那最简单的方式就是在每条DML语句执行后,立刻用@@ROWCOUNT获取受影响行数,然后把语句内容和行数插入到专门的跟踪表中。
首先创建一个用于存储跟踪数据的表:
CREATE TABLE dbo.QueryTracking ( TrackingID INT IDENTITY(1,1) PRIMARY KEY, ExecutionTime DATETIME2(3) DEFAULT SYSUTCDATETIME(), QueryText NVARCHAR(MAX) NOT NULL, AffectedRows INT NOT NULL, SessionID INT DEFAULT @@SPID, ProcedureName NVARCHAR(128) DEFAULT OBJECT_NAME(@@PROCID) );
然后修改你的存储过程,给每条Update/Delete加上记录逻辑:
CREATE PROCEDURE dbo.YourTargetProcedure AS BEGIN SET NOCOUNT ON; -- 示例Delete语句 DELETE FROM dbo.YourTable WHERE Status = 'Expired'; -- 记录这条Delete操作 INSERT INTO dbo.QueryTracking (QueryText, AffectedRows) VALUES ('DELETE FROM dbo.YourTable WHERE Status = ''Expired'';', @@ROWCOUNT); -- 示例Update语句 UPDATE dbo.AnotherTable SET LastUpdated = GETDATE() WHERE ID BETWEEN 1 AND 100; -- 记录这条Update操作 INSERT INTO dbo.QueryTracking (QueryText, AffectedRows) VALUES ('UPDATE dbo.AnotherTable SET LastUpdated = GETDATE() WHERE ID BETWEEN 1 AND 100;', @@ROWCOUNT); END
这个方案的优点是逻辑清晰、数据准确,完全不需要依赖动态管理视图或其他复杂工具;缺点是需要手动修改每个目标存储过程,适合存储过程数量不多的场景。
方案2:用扩展事件无侵入捕获(无需修改存储过程)
如果不想改动现有存储过程,扩展事件是最佳选择——它是SQL Server原生的轻量级监控工具,性能开销极低,能精准捕获你需要的信息。
步骤1:创建扩展事件会话
CREATE EVENT SESSION TrackDMLInStoredProcedures ON SERVER ADD EVENT sqlserver.sql_statement_completed( ACTION( sqlserver.session_id, sqlserver.sql_text, sqlserver.object_name ) WHERE ( sqlserver.is_system = 0 -- 排除系统操作 AND (statement_type = 'UPDATE' OR statement_type = 'DELETE') -- 只抓Update/Delete AND object_type = 'P' -- 只捕获存储过程中的语句 ) ) ADD TARGET package0.event_file(SET filename=N'C:\SQLLogs\TrackDMLInSP.xel') -- 替换成你的存储路径 WITH (STARTUP_STATE=OFF); -- 可选:设置为ON则SQL服务启动时自动开启会话
步骤2:启动会话
ALTER EVENT SESSION TrackDMLInStoredProcedures ON SERVER STATE = START;
步骤3:查询捕获到的数据
SELECT event_data.value('(event/@timestamp)[1]', 'DATETIME2(3)') AS ExecutionTime, event_data.value('(event/action[@name="object_name"]/value)[1]', 'NVARCHAR(128)') AS ProcedureName, event_data.value('(event/action[@name="sql_text"]/value)[1]', 'NVARCHAR(MAX)') AS QueryText, event_data.value('(event/data[@name="row_count"]/value)[1]', 'INT') AS AffectedRows, event_data.value('(event/action[@name="session_id"]/value)[1]', 'INT') AS SessionID FROM ( SELECT CAST(event_data AS XML) AS event_data FROM sys.fn_xe_file_target_read_file('C:\SQLLogs\TrackDMLInSP*.xel', NULL, NULL, NULL) ) AS x ORDER BY ExecutionTime DESC;
这个方案的优点是完全不需要修改业务代码,能捕获所有存储过程里的DML操作;缺点是需要服务器级权限来创建扩展事件,且需要定期清理生成的事件文件。
关于你写的函数的局限性
你尝试用sys.dm_exec_requests来获取查询文本的思路是对的,但这个视图只能返回当前正在执行的请求。当你在存储过程里调用这个函数时,你拿到的只会是存储过程本身的执行语句,而不是里面已经执行完的Update/Delete。另外,标量函数中调用动态管理视图可能会带来不必要的性能开销,不太适合用来做这类跟踪需求。
总结一下:如果能修改存储过程,优先选方案1;如果不能改代码,就用方案2的扩展事件——这两个方案都比用函数的方式更可靠。
内容的提问来源于stack exchange,提问作者Kiran Dhamale

