4

我有一个审计跟踪解决方案,它将触发器中的 INSERTED 或 DELETED 表的内容以及当前用户、时间戳等转储到 XML。对于插入和更新,它记录前者,而对于删除,它记录后者.

但是,为了确定同一记录的两个日志之间发生了什么变化,我需要在审计表上进行自联接以获取前一条记录。这本身并不太糟糕,但是如果我可以在触发器中同时记录 data_from 和 data_to ,性能将会大大提高。

显而易见的解决方案是在 INSERTED 和 DELETED 之间使用内连接进行更新,但这样做的问题是这些表没有也不能被索引,因此数据库需要进行完整的逐行哈希才能生成结果。由于在更新触发器中,INSERTED 和 DELETED 记录中的顺序是相同的,所以我不禁感到必须有某种方法可以在不使用连接和不使用游标的情况下水平组合这两个表。

我已经尝试过的和我知道的不起作用:

  1. 在公用表表达式中使用 ROW_NUMBER 将不起作用 - CTE 未编入索引
  2. 将 INSERTED 和 DELETED 的内容插入到临时表中,对它们进行索引,然后在连接中使用它们——如果我找不到更好的解决方案,这是我目前拥有的一个后备选项。
  3. 使用两个游标,一个用于 INSERTED,一个用于 DELETED——出于性能原因,这是不可能的。
  4. 加入触发器中的审计跟踪表以获取以前的 XML - 也可以,但不如上面的 2,因为我在 DELETED 表中有我需要的数据,我不禁觉得一定有什么东西有用的,我可以用它来做。

有什么想法吗?

4

3 回答 3

3

您是否考虑过使用变更数据捕获功能?它比触发器更有效地捕获更改,并且它是一个异步后台进程,这意味着对实际进行更新的进程的影响最小。

于 2013-05-26T12:34:41.227 回答
2

我最终采用的解决方案是将insertedanddeleted表中的数据传输到索引表变量中,然后从那里使用它们。性能不如 CDC 好,但可以接受且呈线性,而且上市时间要短得多。我编写了一个代码生成器来生成触发器,我在下面包含了一个示例:

IF TRIGGER_NESTLEVEL(OBJECT_ID('TR_su_type_code_audit_log')) > 1 RETURN

DECLARE @user_key INT, @tp INT = 0
IF EXISTS(SELECT 1 FROM deleted) SET @tp += 1
IF EXISTS(SELECT 1 FROM inserted) SET @tp += 2

DECLARE @i TABLE (type_code_key int, audit_data VARCHAR(MAX), PRIMARY KEY (type_code_key))
DECLARE @d TABLE (type_code_key int, audit_data VARCHAR(MAX), PRIMARY KEY (type_code_key))

INSERT INTO @i SELECT type_code_key,
    (SELECT type_code_key, type_code_group, id, description, is_system_reserved, site_key, code_int, 
            code_str, image_index, image_filename FOR XML RAW('audit'))
FROM inserted

INSERT INTO @d SELECT type_code_key,
    (SELECT type_code_key, type_code_group, id, description, is_system_reserved, site_key, code_int, 
            code_str, image_index, image_filename FOR XML RAW('audit'))
FROM deleted


SET @user_key = dbo.f_get_current_user()

IF @tp = 2
BEGIN
    INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, 
            audit_rec_key_2, is_delta, data_to)
    SELECT 'I', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, audit_data
    FROM @i t
END ELSE IF @tp = 1
BEGIN
    INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, 
            audit_rec_key_2, is_delta, data_from)
    SELECT 'D', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, audit_data
    FROM @d t
END ELSE
BEGIN
    INSERT INTO audit_trail (mod_type, mod_date, user_key, audit_rec_table, audit_rec_key, audit_rec_key_1, 
            audit_rec_key_2, is_delta, data_to, data_from)
    SELECT 'U', GETDATE(), @user_key, 'su_type_code', t.type_code_key, NULL, NULL, 0, t.audit_data, d.audit_data
    FROM @i t INNER JOIN @d d ON (t.type_code_key = d.type_code_key)
    WHERE ISNULL(t.audit_data, '') <> ISNULL(d.audit_data, '')
END
SET NOCOUNT OFF;
于 2013-05-30T13:08:40.937 回答
1

我的工作解决方案是将唯一列放入表中,例如 IDENTITY。然后仅在该唯一列上加入 DELETED 和 INSERTED。

于 2014-08-08T12:49:19.737 回答