在我的 table 触发器中记录更改的更有效方式

More efficient way of logging changes in my table trigger

目前我对每个 table 的触发器对于 table 中的每个字段都是这样的:

ALTER TRIGGER [dbo].[trg_Statement] ON [dbo].[tbl_Statement]
FOR INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON
    INSERT INTO tbl_ChangeLog(TableName, ID, FieldName, OldValue, NewValue)
       SELECT  
          'Statement', CU.id, 'id', deleted.id,inserted.id
       FROM
          tbl_Statement CU
       LEFT JOIN 
          inserted on CU.id = inserted.id
       LEFT JOIN 
          deleted on CU.id = deleted.id
       WHERE
          (inserted.id is not null or deleted.id is not null)
          AND IsNull(inserted.id,'') <> IsNull(deleted.id,'')

    INSERT INTO tbl_ChangeLog(TableName, ID, FieldName, OldValue, NewValue)
       SELECT 
          'Statement', CU.id, 'idAccount', deleted.idAccount,inserted.idAccount
       FROM
          tbl_Statement CU
       LEFT JOIN 
          inserted on CU.id = inserted.id
       LEFT JOIN 
          deleted on CU.id = deleted.id
       WHERE
          (inserted.id is not null or deleted.id is not null)
          AND IsNull(inserted.idAccount,'') <> IsNull(deleted.idAccount,'')

    INSERT INTO tbl_ChangeLog(TableName, ID, FieldName, OldValue, NewValue)
       SELECT 
          'Statement', CU.id, 'OpeningBalance', deleted.OpeningBalance,inserted.OpeningBalance
       FROM
          tbl_Statement CU
       LEFT JOIN 
          inserted on CU.id = inserted.id
       LEFT JOIN 
          deleted on CU.id = deleted.id
       WHERE
          (inserted.id is not null or deleted.id is not null)
          AND IsNull(inserted.OpeningBalance,'') <> IsNull(deleted.OpeningBalance,'')
   ...

然而,这非常昂贵,尤其是当有很多字段时,任何人都可以提出一种更有效的方法来跟踪我们的更改日志的更改吗?

我有时会创建一个与 table 相同的 table 我想记录,除了我添加一个新的身份主键和一个日期时间字段来跟踪更改发生的时间。然后,每当原始 table 更改时,我将更改的整行加上当前日期时间插入到跟踪 table 中。

这样做的优点是易于实施,并允许在需要时回滚到以前的一组值。如果您想查看总历史记录,它还允许轻松连接到当前行(如果您经常这样做,请记住索引)。此外,在您的情况下,它只是一个插入,除了获取正确的行之外没有任何逻辑。缺点是每次发生任何变化时它都会存储所有字段,并且您必须维护两个 tables。

您显然可以减少您不想跟踪的字段,或者以其他更符合您需求的自定义方式对其进行修改。例如,如果行被删除,您可能希望在跟踪 table 中保留一个额外的字段。

为了查看旧值和新值,您只需要在 table 中回顾一下日期,看看它是什么时候发生变化的。

我同意 Madison 的观点,您 AUDIT table 中包含整个记录的单行将更有用且性能更高。基础 table 越宽,这一点就越真实。你的情况:

CREATE TRIGGER IUD_Statement_Audit
ON dbo.tbl_Statement AFTER INSERT, UPDATE, DELETE
AS BEGIN

  IF (@@rowCount = 0) RETURN;

  SET NOCOUNT ON;

  INSERT INTO dbo.tbl_Statement_AUDIT
    (id, idAccount, OpeningBalance, insertedOrDeleted, modTime, modId)
  SELECT id, idAccount, OpeningBalance, 'D', GETDATE(), USER_NAME() FROM DELETED
  UNION ALL
  SELECT id, idAccount, OpeningBalance, 'I', GETDATE(), USER_NAME() FROM Inserted

END

这种方法有很多好处,包括:轻松加入 id 列以查看所有 table 的所有更改和触发器成为 copy/paste 练习。一般来说,RDBMS 在宽 tables(一条记录包含所有列)和较少记录的情况下会比窄 [?]table(每个列更改一条记录)表现得更好。您当前的方法将为每条插入、删除和更新的记录创建三个记录。

可以通过 modTime desc, insertedOrDeleted desc 命令审计 table 并执行逆操作来回滚到过去的点...回滚到你想要的位置。

我认为Log Trigger is shorter, faster and clearer. It does not store "types" of changes, such insertions, updates or deletions, instead, it creates an structure called Tuple versioning中描述的方法。插入是没有前面 "version" 的行,删除是没有后续 "version" 的行,中间行是更新。

它的优点是更容易、更自然地检索特定实体在特定时间点的数据。

事实上,很容易了解整个 table 在特定时间点的情况。如果 table 变得混乱并且需要从给定的 "safe" 时间点恢复数据。