SQL 服务器:如何查找执行了哪些行

SQL Server: How to find what lines are executed

我正在为 SQL 服务器开发一个变异测试框架,为此我需要能够计算出当我执行某个存储过程时执行了存储过程、函数或触发器的哪些行。

困难的部分是我想知道从我调用的存储过程中执行的确切行或语句。

通过这样的查询,我可以看到存储的 procedures/triggers/functions 正在被执行,因为我知道当我调用存储过程时我可以使用时间来查看它是否被执行。

SELECT  d.object_id, d.database_id, 
         OBJECT_NAME(object_id, database_id) AS proc_name, 
         MAX( d.last_execution_time) as last_execution_time,
         OBJECT_DEFINITION(object_id) as definition
         FROM sys.dm_exec_procedure_stats AS d 
         WHERE d.database_id = DB_ID()
         GROUP BY  d.object_id, d.database_id, 
         OBJECT_NAME(object_id, database_id) 

我如何找到已执行的 lines/statements,我还必须知道在存储 procedure/trigger/function 的内容中存在 lines/statements 以及它在哪个 shema 中。我必须考虑到可能会使用 IF/ELSE 语句。

有了这些数据,我可以做 2 件重要的事情:

一个可能但不是很好的解决方案是自动更改存储过程以添加一行,将上一行插入 table,但这需要将过程拆分为语句,我不知道该怎么做。

请注意,我无法更改用户想要使用我的框架测试的代码。我可以搜索模式并替换,但不能手动更改程序。

编辑: 让我们重新定义这个问题:如何以不依赖于代码风格的方式将存储过程定义拆分为不同的语句? 以及如何在找到的语句之间添加新语句?

编辑:在 SO post 中,我找到了一种跟踪语句执行的方法,但我还不能过滤它。

您可以:

  • 给你调用的每个存储过程添加一个@DEBUG参数,或者
  • 记录你想要的一切,或者
  • 只在需要时记录。

使用@Debug参数,你可以默认它为OFF,然后当你想跟踪你的语句时用ON调用它,代码如下:

IF (@Debug = 1) PRINT 'your tracing information goes here';

如果您想记录所有内容,请创建一个日志 table 并在您需要知道执行了哪条语句的任何地方插入一行,例如:

DECLARE @log AS TABLE (msg VARCHAR(MAX));

INSERT INTO @log VALUES('your tracing information goes here');

或者您可以组合它们:

IF (@Debug = 1) INSERT INTO @log VALUES('your tracing information goes here');

当然,即使您不这样做,这些也会影响性能 output/log。

所以扩展事件是解决方案,我是这样做的:

IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='testMSSQLTrace')  
   DROP EVENT SESSION testMSSQLTrace ON SERVER;  

DECLARE @cmd VARCHAR(MAX) = '';
SELECT @cmd = 'CREATE EVENT SESSION testMSSQLTrace 
ON SERVER
    ADD EVENT sqlserver.sp_statement_completed
        (WHERE (sqlserver.database_name = N''' + DB_NAME() + '''))
    ADD TARGET package0.ring_buffer
        WITH (
            MAX_MEMORY = 2048 KB,
            EVENT_RETENTION_MODE = NO_EVENT_LOSS,
            MAX_DISPATCH_LATENCY = 3 SECONDS,
            MAX_EVENT_SIZE = 0 KB,
            MEMORY_PARTITION_MODE = NONE,
            TRACK_CAUSALITY = OFF,
            STARTUP_STATE = OFF
        );'

EXEC (@cmd)

这会创建一个可以在每个语句完成后触发的事件,这是动态完成的以在数据库上进行过滤

然后我有 3 个程序可以轻松控制此事件

/*******************************************************************************************
    Starts the statement trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_StartTrace
AS
BEGIN 
    ALTER EVENT SESSION testMSSQLTrace
          ON SERVER
        STATE = START; 
END
GO

/*******************************************************************************************
    Ends the statement trace, this also clears the trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_StopTrace
AS
BEGIN
    ALTER EVENT SESSION testMSSQLTrace
          ON SERVER
        STATE = STOP; 
END
GO


/*******************************************************************************************
    Saves the statements trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_SaveTrace
AS
BEGIN
    DECLARE @xml XML;

    SELECT @xml = CAST(xet.target_data AS xml)
        FROM sys.dm_xe_session_targets AS xet INNER JOIN sys.dm_xe_sessions AS xe ON (xe.address = xet.event_session_address)  
        WHERE xe.name = 'testMSSQLTrace'  

    INSERT INTO testMSSQL.StatementInvocations (testProcedure, procedureName, lineNumber, statement)
        SELECT testMSSQL.GetCurrentTest(), 
            OBJECT_NAME(T.c.value('(data[@name="object_id"]/value)[1]', 'int')),
            T.c.value('(data[@name="line_number"]/value)[1]', 'int'), 
            T.c.value('(data[@name="statement"]/value)[1]', 'VARCHAR(900)')
        FROM @xml.nodes('RingBufferTarget/event') T(c)
        WHERE T.c.value('(data[@name="nest_level"]/value)[1]', 'int') > 3

END
GO

这些程序分别启动和停止跟踪,最后一个程序将结果存储在 table 中,它在嵌套级别进行过滤,因此我自己的代码不会被跟踪。

最后我有点像这样使用它:

start trace
start tran/savepoint
run SetUp (users code)
run test (users code)
save trace
save trace to variable
rollback tran (also catch errors and stuff like that)
save variable back to table so the trace is not rolled back

特别感谢@Jeroen Mosterd 最初在此 SO post

中提出了此解决方案的提案