44

我不知道这些要求是否是标准的,但我想知道是否有可以执行以下操作的解决方案:

  • 对于一组指定的表,在相关表的审计版本中更改记录之前保留一份记录的副本。

我宁愿不必为每个表都编写代码。我想知道是否有一个可以安装在 SQL Server 之上的解决方案来为您执行此操作?

4

8 回答 8

38

有很多方法可以做到这一点;这取决于您使用的 SQL Server 版本。

这里有几个

于 2013-07-09T11:17:10.263 回答
13

我创建了为 XML 执行此操作的触发器,我们可以将所有表记录到同一个表中,使其更加灵活

CREATE TABLE [dbo].[AuditAll] (
    AuditId    int           NOT NULL IDENTITY(1,1),
    [DateTime] datetime      NOT NULL,
    TableName  nvarchar(255) NOT NULL,
    AuditEntry xml           NULL,

    CONSTRAINT [PK_AuditAll] PRIMARY KEY CLUSTERED ( AuditId ASC )
)

我只需要“旧”值,所以我只存储已删除的表,无论如何都可以在表中看到插入的表。

CREATE TRIGGER AuditSimple 
    ON Simple
    AFTER INSERT,DELETE,UPDATE
AS 
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

IF (SELECT COUNT(*) FROM deleted) > 0 
begin
    Declare @AuditMessage XML
    --set valut to all xml from deleted table
    set @AuditMessage = (select * from deleted for xml auto) 

    insert into AuditAll( DateTime, TableName, AuditEntry ) 
        values ( GetDate(), 'Simple', @AuditMessage )
end

END
GO

我想这可以很容易地在 sp_foreach 中调用来为数据库中的每个表创建它,但我们目前不需要它,只要记住更改你的表名

干杯

于 2014-02-25T14:44:55.687 回答
12

数据库表

假设我们有一个Book表,其审计日志信息必须存储在一个BookAuditLog表中,如下面的类图所示:

使用触发器的 SQL Server 审计日志记录

BookAuditLog表是这样创建的:

CREATE TABLE BookAuditLog (
    BookId bigint NOT NULL,
    OldRowData nvarchar(1000) CHECK(ISJSON(OldRowData) = 1),
    NewRowData nvarchar(1000) CHECK(ISJSON(NewRowData) = 1),
    DmlType varchar(10) NOT NULL CHECK (DmlType IN ('INSERT', 'UPDATE', 'DELETE')),
    DmlTimestamp datetime NOT NULL,
    DmlCreatedBy varchar(255) NOT NULL,
    TrxTimestamp datetime NOT NULL,
    PRIMARY KEY (BookId, DmlType, DmlTimestamp)
) 

BookAuditLog列存储以下数据:

  • BookId列存储Book为其创建此日志事件的关联行的标识符。
  • 在执行 INSERT、UPDATE 或 DELETE 语句之前OldRowData存储记录状态的 JSON 表示。Book
  • 在执行 INSERT、UPDATE 或 DELETE 语句后NewRowData存储记录状态的 JSON 表示。Book
  • DmlType是一个枚举列,存储创建、更新或删除给定Book行的 DML 语句类型。
  • DmlTimestamp存储 DML 语句执行时间戳。
  • DmlCreatedBy存储发出 INSERT、UPDATE 或 DELETE DML 语句的用户。
  • TrxTimestamp存储更改Book记录的事务的时间戳。

INSERT、UPDATE 和 DELETE 触发器

要捕获 INSERT、UPDATE 和 DELETE DML 语句,我们需要创建三个将在BookAuditLog表中插入记录的数据库触发器。

为了拦截表上的 INSERT 语句Book,我们将创建TR_Book_Insert_AuditLog触发器:

CREATE TRIGGER TR_Book_Insert_AuditLog ON Book
FOR INSERT AS 
BEGIN
    DECLARE @loggedUser varchar(255)
    SELECT @loggedUser = CAST(SESSION_CONTEXT(N'loggedUser') AS varchar(255))
    
    DECLARE @transactionTimestamp datetime = SYSUTCdatetime()
    
    INSERT INTO BookAuditLog (
        BookId,
        OldRowData,
        NewRowData,
        DmlType,
        DmlTimestamp,
        DmlCreatedBy,
        TrxTimestamp
    )
    VALUES(
        (SELECT id FROM Inserted),
        null,
        (SELECT * FROM Inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
        'INSERT',
        CURRENT_TIMESTAMP,
        @loggedUser,
        @transactionTimestamp
    );
END

为了捕获Book记录上的 UPDATE 语句,我们将创建以下TR_Book_Update_AuditLog触发器:

CREATE TRIGGER TR_Book_Update_AuditLog ON Book
FOR UPDATE AS 
BEGIN
    DECLARE @loggedUser varchar(255)
    SELECT @loggedUser = CAST(SESSION_CONTEXT(N'loggedUser') AS varchar(255))
    
    DECLARE @transactionTimestamp datetime = SYSUTCdatetime()
    
    INSERT INTO BookAuditLog (
        BookId,
        OldRowData,
        NewRowData,
        DmlType,
        DmlTimestamp,
        DmlCreatedBy,
        TrxTimestamp
    )
    VALUES(
        (SELECT id FROM Inserted),
        (SELECT * FROM Deleted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
        (SELECT * FROM Inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
        'UPDATE',
        CURRENT_TIMESTAMP,
        @loggedUser,
        @transactionTimestamp
    );
END

要拦截Book表行上的 DELETE 语句,我们将创建以下TR_Book_Delete_AuditLog触发器:

CREATE TRIGGER TR_Book_Delete_AuditLog ON Book
FOR DELETE AS 
BEGIN
    DECLARE @loggedUser varchar(255)
    SELECT @loggedUser = CAST(SESSION_CONTEXT(N'loggedUser') AS varchar(255))
    
    DECLARE @transactionTimestamp datetime = SYSUTCdatetime()
    
    INSERT INTO BookAuditLog (
        BookId,
        OldRowData,
        NewRowData,
        DmlType,
        DmlTimestamp,
        DmlCreatedBy,
        TrxTimestamp
    )
    VALUES(
        (SELECT id FROM Deleted),
        (SELECT * FROM Deleted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
        null,
        'DELETE',
        CURRENT_TIMESTAMP,
        @loggedUser,
        @transactionTimestamp
    );
END

演示时间

Book表上执行 INSERT 语句时:

INSERT INTO Book (
    Author, 
    PriceInCents, 
    Publisher, 
    Title, 
    Id
)
VALUES (
    'Vlad Mihalcea', 
    3990, 
    'Amazon', 
    'High-Performance Java Persistence 1st edition', 
    1
)

我们可以看到插入了一条记录,该记录BookAuditLog捕获了刚刚在Book表上执行的 INSERT 语句:

| BookId | OldRowData | NewRowData                                                                                                                         | DmlType | DmlTimestamp            | DmlCreatedBy  | TrxTimestamp            |
|--------|------------|------------------------------------------------------------------------------------------------------------------------------------|---------|-------------------------|---------------|-------------------------|
| 1      |            | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | INSERT  | 2020-11-08 08:40:28.343 | Vlad Mihalcea | 2020-11-08 06:40:28.347 |

更新Book表格行时:

UPDATE Book 
SET PriceInCents = 4499 
WHERE Id = 1

我们可以看到,表BookAuditLog上的 AFTER UPDATE 触发器将添加一条新记录Book

| BookId | OldRowData                                                                                                                         | NewRowData                                                                                                                         | DmlType | DmlTimestamp            | DmlCreatedBy  | TrxTimestamp            |
|--------|------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|---------|-------------------------|---------------|-------------------------|
| 1      |                                                                                                                                    | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | INSERT  | 2020-11-08 08:40:28.343 | Vlad Mihalcea | 2020-11-08 06:40:28.347 |
| 1      | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":4499,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | UPDATE  | 2020-11-08 08:43:22.803 | Vlad Mihalcea | 2020-11-08 06:43:22.807 |

删除Book表格行时:

DELETE FROM Book 
WHERE Id = 1

一条新记录被表BookAuditLog上的 AFTER DELETE 触发器添加到Book

| BookId | OldRowData                                                                                                                         | NewRowData                                                                                                                         | DmlType | DmlTimestamp            | DmlCreatedBy  | TrxTimestamp            |
|--------|------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|---------|-------------------------|---------------|-------------------------|
| 1      |                                                                                                                                    | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | INSERT  | 2020-11-08 08:40:28.343 | Vlad Mihalcea | 2020-11-08 06:40:28.347 |
| 1      | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":3990,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":4499,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} | UPDATE  | 2020-11-08 08:43:22.803 | Vlad Mihalcea | 2020-11-08 06:43:22.807 |
| 1      | {"Id":1,"Author":"Vlad Mihalcea","PriceInCents":4499,"Publisher":"Amazon","Title":"High-Performance Java Persistence 1st edition"} |                                                                                                                                    | DELETE  | 2020-11-08 08:44:25.630 | Vlad Mihalcea | 2020-11-08 06:44:25.633 |
于 2020-11-28T12:51:25.697 回答
6

您可以尝试基于第三方点击式触发器的解决方案,例如ApexSQL Audit - SQL Server 数据库的审计工具,它捕获数据库上发生的数据更改,包括有关谁进行更改的信息,哪些对象是受影响的时间,以及有关用于进行更改的 SQL 登录、应用程序和主机的信息。它将所有捕获的信息存储在中央存储库中,并以打印友好的格式导出

免责声明:我在 ApexSQL 担任产品支持工程师

于 2014-04-04T17:40:00.900 回答
0

看看触发器。这些可用于实现满足您要求的某些东西。

于 2013-07-09T11:05:09.313 回答
0

看看这篇文章——SQL Server 2008中的审计,它完美地利用了 SQL Server 2008 中已经存在的审计功能。

我还必须提到@Microtechie 的答案指向了一些很棒的文章。阅读它们并决定哪一个更容易适应。

于 2013-07-09T11:36:12.053 回答
0

我看到了 3 种方法来实现这一点:

  1. 触发器是最好的解决方案。
  2. 您可以为表或数据库实现复制/日志传送,这将始终是该表/数据库的几毫秒/秒的旧副本。
  3. 根据您需要的旧副本的持续时间安排差异备份。

使用选项“2”,如果出现任何问题,您可以立即关闭复制/日志传送并获得几秒钟前的准确数据副本。使用选项“3”,例如,您的差异备份频率为每 5 分钟一次,那么如果出现任何问题,您可以恢复 5 分钟前的正确数据的旧副本。

于 2019-10-31T18:54:52.973 回答
-1

使用数据库快照,您可以在那一刻保留数据的只读副本。如果需要,您还可以通过日志备份在特定时间段恢复您的信息。

您还可以从日志中读取信息以检索更改的信息。

您不喜欢的另一个解决方案是使用触发器跟踪更改,但它可能需要在每个表上工作。您还可以启用 Change Data Capture 功能来检测更改,该功能也需要为每个表启用,但它需要的代码比触发器少。

最后,像Apex SQL Trigger这样的第三方工具可以通过几次点击和配置自动完成这项工作。

于 2015-01-29T23:02:21.287 回答