将有关谁删除记录的信息传递给删除触发器

web*_*orm 13 trigger sql-server audit sql-server-2012

在设置审计跟踪时,跟踪谁在表中更新或插入记录没有问题,但是,跟踪谁删除记录似乎更成问题。

我可以通过在插入/更新字段中包含“UpdatedBy”来跟踪插入/更新。这允许 INSERT/UPDATE 触发器通过inserted.UpdatedBy. 但是,使用删除触发器不会插入/更新数据。有没有办法将信息传递给删除触发器,以便它知道谁删除了记录?

这是一个插入/更新触发器

ALTER TRIGGER [dbo].[trg_MyTable_InsertUpdate] 
ON [dbo].[MyTable]
FOR INSERT, UPDATE
AS  

INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
VALUES (inserted.ID, inserted.LastUpdatedBy)
FROM inserted 
Run Code Online (Sandbox Code Playgroud)

使用 SQL Server 2012

Sol*_*zky 13

有没有办法将信息传递给删除触发器,以便它知道谁删除了记录?

是的:通过使用一个非常酷的(并且未被充分利用的功能)称为CONTEXT_INFO. 它本质上是存在于所有作用域中并且不受事务约束的会话内存。它可用于将信息(任何信息——好吧,任何适合有限空间的信息)传递给触发器以及在 sub-proc/EXEC 调用之间来回传递。我以前在这种完全相同的情况下使用过它。

用下面的测试看看它是如何工作的。请注意,我转换成CHAR(128)CONVERT(VARBINARY(128), ..。这是力空白填充,使其更容易转换回VARCHAR得到它的时候CONTEXT_INFO(),因为VARBINARY(128)是右侧填充0x00秒。

SELECT CONTEXT_INFO();
-- Initially = NULL

DECLARE @EncodedUser VARBINARY(128);
SET @EncodedUser = CONVERT(VARBINARY(128),
                            CONVERT(CHAR(128), 'I deleted ALL your records! HA HA!')
                          );
SET CONTEXT_INFO @EncodedUser;

SELECT CONTEXT_INFO() AS [RawContextInfo],
       RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())) AS [DecodedUser];
Run Code Online (Sandbox Code Playgroud)

结果:

0x492064656C6574656420414C4C20796F7572207265636F7264732120484120484121202020202020...
I deleted ALL your records! HA HA!
Run Code Online (Sandbox Code Playgroud)

把它放在一起:

  1. 该应用程序应该调用一个“删除”存储过程,该过程传入正在删除记录的用户名(或其他任何内容)。我假设这已经是正在使用的模型,因为听起来您已经在跟踪插入和更新操作。

  2. “删除”存储过程执行以下操作:

    DECLARE @EncodedUser VARBINARY(128);
    SET @EncodedUser = CONVERT(VARBINARY(128),
                                CONVERT(CHAR(128), @UserName)
                              );
    SET CONTEXT_INFO @EncodedUser;
    
    -- DELETE STUFF HERE
    
    Run Code Online (Sandbox Code Playgroud)
  3. 审计触发器执行以下操作:

    -- Set the INT value in LEFT (currently 50) to the max size of [UserWhoMadeChanges]
    INSERT INTO AuditTable (IdOfRecordedAffected, UserWhoMadeChanges) 
       SELECT del.ID, COALESCE(
                         LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50),
                         '<unknown>')
       FROM DELETED del;
    
    Run Code Online (Sandbox Code Playgroud)
  4. 请注意,正如@SeanGallardy 在评论中指出的那样,由于其他程序和/或临时查询从该表中删除记录,有可能:

    • CONTEXT_INFO尚未设置,仍然是NULL

      出于这个原因,我更新了上面的内容INSERT INTO AuditTable以使用 aCOALESCE来默认值。或者,如果您不想要默认值并需要一个名称,那么您可以执行类似以下操作:

      DECLARE @UserName VARCHAR(50); -- set to the size of AuditTable.[UserWhoMadeChanges]
      SET @UserName = LEFT(RTRIM(CONVERT(VARCHAR(128), CONTEXT_INFO())), 50);
      
      IF (@UserName IS NULL)
      BEGIN
         ROLLBACK TRAN; -- cancel the DELETE operation
         RAISERROR('Please set UserName via "SET CONTEXT_INFO.." and try again.', 16 ,1);
      END;
      
      -- use @UserName in the INSERT...SELECT
      
      Run Code Online (Sandbox Code Playgroud)
    • CONTEXT_INFO已设定为一个值不是一个有效的用户名,因此可能会超过大小AuditTable.[UserWhoMadeChanges]字段:

      出于这个原因,我添加了一个LEFT功能来确保从里面抓取的任何东西CONTEXT_INFO都不会破坏INSERT. 如代码中所述,您只需将 设置50UserWhoMadeChanges字段的实际大小。


SQL SERVER 2016 及更新版本的更新

SQL Server 2016 添加了此每会话内存的改进版本:会话上下文。新的会话上下文本质上是一个键值对的哈希表,其中“键”是类型sysname(即NVARCHAR(128)),“值”是SQL_VARIANT。意义:

  1. 现在有一个值的分离,因此不太可能与其他用途发生冲突
  2. 您可以存储各种类型,不再需要担心通过获取值时的奇怪行为CONTEXT_INFO()(有关详细信息,请参阅我的帖子:为什么 CONTEXT_INFO() 不返回由 SET CONTEXT_INFO 设置的确切值?
  3. 你得到了很多更多的空间:8000字节每“价值”最高,可达所有按键256KB总(与128的最大字节CONTEXT_INFO

有关详细信息,请参阅以下文档页面:

  • 我是说安全作为事后的想法是主要问题,这不是解决它的工具。备忘录结构或其他不会破坏应用程序的用途,当然我没有问题。这绝对是不使用它的原因。YMMV 但我永远不会使用如此不稳定和非结构化的东西来处理重要的事情,例如安全性。总体而言,使用任何类型的共享用户可写存储来确保安全都是一个糟糕的主意。在大多数情况下,适当的设计将消除对此类事物的需求。 (2认同)

Dav*_*ett 5

您不能那样做,除非您希望记录 SQL 服务器用户 ID 而不是应用程序级别的用户 ID。

您可以通过一个名为 DeletedBy 的列并根据需要进行设置来进行软删除,然后您的更新触发器可以进行真正的删除(或存档记录,我通常会在可能且合法的情况下避免硬删除)以及更新您的审计跟踪. 要以这种方式强制删除,请定义一个on delete引发错误的触发器。如果您不想在物理表中添加列,您可以定义一个视图来添加列并定义instead of触发器来处理更新基表,但这可能有点矫枉过正。