【问题标题】:Create Trigger to log SQL that affected table?创建触发器以记录影响表的 SQL?
【发布时间】:2011-03-06 21:39:16
【问题描述】:

我试图找出更新列值的原因,但我对应用程序知之甚少。快速浏览一下,我注意到大约 90% 的应用程序业务逻辑是在数据库上处理的。不用说 SP、函数和触发器的深度是疯狂的。

我想在有问题的表上创建一个触发器,该触发器将记录影响该表的 SQL。什么 SQL 可用于在正在更新的表的上下文中获取执行的 SQL?

详情: 微软 SQL Server 2008

谢谢!!

【问题讨论】:

    标签: sql sql-server sql-server-2008 triggers extended-events


    【解决方案1】:

    SQL Server 2008 引入了一项称为更改数据捕获 (CDC) 的新功能,而不是使用触发器。 Read more about it here.

    【讨论】:

    • 非常酷的功能,但我并不适用于我的情况。我需要能够看到正在接触表的 SQL,以便我可以进一步调查它。我知道为此会想到分析器,它是我的主要工具,但在这个特定项目中,由于某些逻辑,会触发超过 30-50 个数据库项。
    【解决方案2】:

    【讨论】:

    • 目前这看起来更像是我需要的。我将在接下来的几分钟内对此进行更多研究。谢谢凯德!
    • 我投票支持与 Cade 关联的功能。请参阅我发布的问题的答案。您链接到的手动实现的“调用堆栈”对我不起作用,因为我必须更改每个 SP、视图和触发器。它也不能与函数一起使用。
    • 也可以使用扩展事件获得一种调用栈。虽然我自己没有尝试过。 sqlblog.com/blogs/extended_events/archive/2010/05/07/…
    【解决方案3】:

    我的解决方案

    我在有问题的表上添加了一个触发器,该触发器记录了我通过sys.dm_exec_sql_textsys.dm_exec_query_stats 的时间戳缩小范围。这很快就确定了我正在寻找的东西。原来有一些我不知道的触发器在 UPDATE 之后更新数据。

    SELECT 
        qStats.last_execution_time AS [ExecutedAt],
        qTxt.[text] AS [Query], qTxt.number
    FROM
        sys.dm_exec_query_stats AS qStats
        CROSS APPLY
        sys.dm_exec_sql_text(qStats.sql_handle) AS qTxt
    WHERE
        qTxt.[dbid] = @DbId
        AND qTxt.[text] like '%UPDATE%'
        AND qStats.last_execution_time between @StartExecutionSearchTime and @EndExecutionSearchTime
    ORDER BY
        qStats.last_execution_time DESC 
    

    【讨论】:

      【解决方案4】:

      我意识到这个问题已经解决了,但是我对如何使用 SQL Server 2008 扩展事件来解决它很感兴趣。这是我第一次玩 XEvents,所以我相信还有很多需要改进的地方!

      设置测试数据库的脚本

      CREATE TABLE [dbo].[TableWithMysteryUpdate](
          [Period] [int] NOT NULL,
          [ColumnThatWillBeUpdated] [int]  NOT NULL
      ) ON [PRIMARY]
      GO
      INSERT [dbo].[TableWithMysteryUpdate] ([Period], [ColumnThatWillBeUpdated]) VALUES (1, 20)
      INSERT [dbo].[TableWithMysteryUpdate] ([Period], [ColumnThatWillBeUpdated]) VALUES (2, 23)
      
      GO
      
      CREATE TABLE [dbo].[TestTable](
          [foo] [int] IDENTITY(1,1) NOT NULL,
          [bar] [nchar](10) NOT NULL,
       CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
      (
          [foo] ASC
      )
      )
      GO
      
      CREATE TRIGGER [dbo].[triggerCausingMysteryUpdate] 
         ON  [dbo].[TestTable] 
         AFTER INSERT
      AS 
      BEGIN
          SET NOCOUNT ON;
      UPDATE [dbo].[TableWithMysteryUpdate]
         SET [Period] = [Period]+1
      
      END
      GO
      
      CREATE PROCEDURE [dbo].[Proc4]
      AS
      BEGIN
      INSERT INTO [dbo].[TestTable]
                 (
                 [bar])
           VALUES
                 ('Test')
      END
      
      GO
      
      CREATE PROCEDURE [dbo].[Proc3]
      AS
      BEGIN
      EXEC dbo.Proc4
      END
      GO
      
      CREATE PROCEDURE [dbo].[Proc2]
      AS
      BEGIN
      EXEC dbo.Proc3
      END
      GO
      
      CREATE PROCEDURE [dbo].[Proc1]
      AS
      BEGIN
      EXEC dbo.Proc2
      END
      

      所以情况是 TableWithMysteryUpdate 正在更新,但我不确定是什么。我将添加一个不执行任何操作的更新触发器,以便能够过滤此对象。

      CREATE TRIGGER [dbo].[triggerAfterUpdate] 
         ON  [dbo].[TableWithMysteryUpdate] 
         AFTER UPDATE
      AS 
      BEGIN
          SET NOCOUNT ON;
      
      END
      

      然后运行脚本来创建 XEvents 会话,触发最终会向下调用堆栈导致更新发生的过程,然后停止会话。

      USE TestDB    
      
      DECLARE @DynSql nvarchar(max)
      
      SET @DynSql = '
      
      IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name=''test_trace'')
          DROP EVENT SESSION [test_trace] ON SERVER;
      CREATE EVENT SESSION [test_trace]
      ON SERVER
      ADD EVENT sqlserver.sql_statement_completed(
           ACTION (package0.callstack, sqlserver.session_id, sqlserver.sql_text, sqlserver.tsql_stack)
           WHERE (object_id = ' + cast(object_id('[dbo].[triggerAfterUpdate]') as varchar(10)) + ') 
           )
      ,
      ADD EVENT sqlserver.sp_statement_completed(
           ACTION (package0.callstack, sqlserver.session_id, sqlserver.sql_text, sqlserver.tsql_stack)
           WHERE (object_id = ' + cast(object_id('[dbo].[triggerAfterUpdate]') as varchar(10)) + ') 
           )
      ADD TARGET package0.asynchronous_file_target
      (set filename = ''c:\temp\test_trace.xel'' , metadatafile = ''c:\temp\test_trace.xem'')
      ALTER EVENT SESSION [test_trace] ON SERVER STATE = START
      '
      
      EXEC sp_executesql @DynSql
      GO
      
      EXEC  dbo.Proc1
      
      GO
      
      ALTER EVENT SESSION [test_trace] ON SERVER STATE = STOP
      

      跟踪数据可以用

      读取
      SELECT CONVERT (XML, event_data) AS data
              FROM sys.fn_xe_file_target_read_file ('C:\Temp\test_trace*.xel', 'C:\Temp\test_trace*.xem', NULL, NULL)
      

      调用栈部分是

       <action name="tsql_stack" package="sqlserver">
          <value>&lt;frame level='1' handle='0x03000800E8EA0D0612E4EB00A59D00000000000000000000' line='6' offsetStart='228' offsetEnd='264'/&gt;
      &lt;frame level='2' handle='0x03000800921155002C81E700A59D00000000000000000000' line='8' offsetStart='258' offsetEnd='398'/&gt;
      &lt;frame level='3' handle='0x03000800CB3549012F81E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='284'/&gt;
      &lt;frame level='4' handle='0x03000800045A3D022F81E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='120'/&gt;
      &lt;frame level='5' handle='0x030008003D7E31033081E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='120'/&gt;
      &lt;frame level='6' handle='0x0300080076A225043081E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='120'/&gt;
      &lt;frame level='7' handle='0x010008002E775203603D9A0D000000000000000000000000' line='2' offsetStart='4' offsetEnd='-1'/&gt;</value>
          <text />
        </action>
      

      加入 DMV

      WITH CapturedResults AS
          ( SELECT data.value ( '(/event/@timestamp)[1]', 'DATETIME')                                        AS [TIME],
              data.value ( '(/event/data[@name=''cpu'']/value)[1]', 'INT')                                   AS [CPU (ms)],
              CONVERT (FLOAT, data.value ('(/event/data[@name=''duration'']/value)[1]', 'BIGINT')) / 1000000 AS [Duration (s)],
              data.value ( '(/event/action[@name=''sql_text'']/value)[1]', 'VARCHAR(MAX)')                   AS [SQL STATEMENT],
              CAST(data.value('(/event/action[@name="tsql_stack"]/value)[1]','varchar(MAX)') AS XML)         AS [stack_xml]
          FROM (SELECT CONVERT (XML, event_data) AS                                                             data
              FROM sys.fn_xe_file_target_read_file ('C:\Temp\test_trace*.xel', 'C:\Temp\test_trace*.xem', NULL, NULL)
              )
              entries
          )
          ,
          StackData AS
          ( SELECT frame_xml.value('(./@level)', 'int')      AS [frame_level],
              frame_xml.value('(./@handle)', 'varchar(MAX)') AS [sql_handle],
              frame_xml.value('(./@offsetStart)', 'int')     AS [offset_start],
              frame_xml.value('(./@offsetEnd)', 'int')       AS [offset_end]
          FROM CapturedResults CROSS APPLY stack_xml.nodes('//frame') N (frame_xml)
          )
      SELECT sd.frame_level,
          object_name(st.objectid, st.dbid) AS ObjectName,
          SUBSTRING(st.text, (sd.offset_start/2)+1, ((
                  CASE sd.offset_end
                      WHEN -1
                      THEN DATALENGTH(st.text)
                      ELSE sd.offset_end
                  END - sd.offset_start)/2) + 1) AS statement_text,
          qp.query_plan,
          qs2.creation_time,
          qs2.last_execution_time,
          qs2.execution_count
      FROM StackData AS sd CROSS APPLY sys.dm_exec_sql_text(CONVERT(VARBINARY(MAX),sd.sql_handle,1)) AS st
          LEFT OUTER JOIN sys.dm_exec_query_stats qs2
          ON  qs2.sql_handle = CONVERT(VARBINARY(MAX),sd.sql_handle,1) OUTER APPLY sys.dm_exec_query_plan(CONVERT(VARBINARY(MAX),qs2.plan_handle,1)) AS qp
      

      给出结果(显示整个 tsql 调用堆栈)

      【讨论】:

      • 我不知道自 10 年以来发生了什么变化,但在 MSSQL 2012 上,您在 xml 事件跟踪上的最后一个查询没有返回任何内容。不过,更简单的SELECT CONVERT (XML, event_data) AS data FROM sys.fn_xe_file_target_read_file 会返回大量事件。
      【解决方案5】:

      除了使用将在数据更改发生时触发的触发器之外,您还可以使用 SQL Server 跟踪并在 SQL Profiler 或跟踪 DML 更改执行代码的第三方审计工具中分析它们。

      使用ApexSQL Comply,您可以将审核范围缩小到特定的数据库对象(本例中为表)和事件类型。

      报告显示各种有用的信息,包括启动更改所执行的 SQL 语句。

      免责声明:我作为支持工程师在 ApexSQL 工作

      【讨论】:

        猜你喜欢
        • 2019-10-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-07
        • 1970-01-01
        • 1970-01-01
        • 2011-10-23
        • 2011-10-23
        相关资源
        最近更新 更多