【问题标题】:How to keep an audit/history of changes to the table如何保留对表的更改的审计/历史记录
【发布时间】:2009-11-25 16:27:38
【问题描述】:

我被要求创建一个简单的 DataGrid 样式的应用程序来编辑数据库的单个表,这很容易。但部分请求是创建对所做更改、进行更改的人员以及日期/时间的审计跟踪。

你会怎么解决这种事情?

(我将在 VS2008 中使用 C#、连接到 SQL Server 2005 的 ADO.NET、WPF 和 Xceed 的 DataGrid,如果有什么不同的话。)

【问题讨论】:

  • 您要审核对内容或表结构本身的更改吗?我怀疑您的意思是内容而不是架构,但是“编辑数据库的单个表”这句话有点可疑。架构更改也可以通过触发器进行审计 FYI ;)
  • 所需审核的粒度是多少?您可以在元组(行)级别(即某些属性已更改,但用户对细节不感兴趣)或行 AND 属性(列)级别(即用户对每个属性更改感兴趣)执行此操作。它们意味着不同的架构设计。
  • 此外,即使您确信您的程序是唯一访问该表的程序,也要假设否则,否则未来的需求变化会抓住您。
  • 啊,是的……好问题。出于我的目的,我只需要保留对低流量数据库中几个特定表的更改的历史记录(配置值更改、日期/时间和用户名)。如果更新更频繁或需要审核架构更改等,显然会更复杂。

标签: sql-server tsql ado.net triggers


【解决方案1】:

创建审计跟踪的常用方法有两种。

  1. 编码您的数据访问层。
  2. 在数据库本身中使用触发器。

两者各有利弊。有些人更喜欢其中之一。这通常取决于您可以预期的应用类型和数据库使用类型。

如果您在 DA 层中执行此操作,这几乎取决于您。您只需将代码添加到保存到数据库的每个方法,也可以保存更改日志。这个审计代码可以在你的 DA 层代码中,或者甚至在你数据库中的存储过程中,如果你对所有东西都使用存储过程。本质上,前提是相同的,只要您对数据库进行更改,就记录该更改。

如果您想沿触发器路线走下去,您可以为每个表编写自定义触发器,或者设计一个更通用的触发器,在许多表上都一样。在审计触发器上查看 this article。这通过在进行更改时触发触发器来工作,并且触发器会记录更改。请记住,如果要审核 SELECT 语句,则不能使用触发器,您必须在代码/存储过程审核中执行此操作。还值得记住的是,根据您的数据库,触发器可能不会在所有情况下触发。例如,大多数数据库不会在 TRUNCATE 语句期间触发触发器。检查您的触发器是否在您需要审核的任何情况下被触发。

或者,您也可以看看使用service broker 在专用机器上进行异步审计。这比较复杂,需要进行一些配置才能设置。

无论采用哪种方式,您都需要决定审核日志将采用的格式。通常,您会将此日志保存在数据库中,但您可以将其保存在日志文件或任何适合您要求的文件中。您可以使用单个审计表来记录所有更改,或者您可以为每个要审计的主表创建一个审计表。对于大规模实施,您甚至可以考虑将审计表放在一个完全独立的数据库中。如果您登录到表中,通常会有一个“更改类型”字段,该字段指示审核的更改是插入、更新还是删除更改样式,以及更改的数据、进行更改的用户和日期/时间做出了改变。不要忘记包含更新样式更改的新旧数据。

【讨论】:

  • 在应用程序/存储过程中审核代码是个坏主意:访问该表的其他程序/交互式用户可能会绕过审核。
  • 优秀——这就是我需要的方向。关于审计触发器的文章也写得很好(虽然我不得不挖掘一点才能让链接工作:weblogs.asp.net/jgalloway/archive/2008/01/27/…)。
  • 永远不要对 inteh 数据访问层进行审计,那只是自找麻烦。还有其他方法可以更改需要记录的数据。如果您想要完整的审计跟踪,这必须在数据库中完成。
  • mmm.. 该文章中详细介绍的设计是实体-属性-值模型 (EAV) (en.wikipedia.org/wiki/Entity-attribute-value_model)。推荐阅读:tonyandrews.blogspot.com/2004/10/…simple-talk.com/opinion/opinion-pieces/bad-carma
  • 我认为重要的是考虑到应用这些技术。两者都不是特别适合非常大规模的高负载数据库。在某些情况下,对数据访问代码进行审计是可行的,但您必须考虑其缺点,正如 MaD70 所说,可以绕过审计。同样,使用通用样式触发器,如果​​方案发生更改,您可能会在处理数据时遇到问题,因为审计跟踪可能会更长地映射到方案。没有完美的方法,但你只需要仔细考虑每一个,这里有几个很好的答案。
【解决方案2】:

同样使用触发器。

任何考虑软删除的人都应该阅读 Richard Dingwall 的 The trouble with soft delete

【讨论】:

  • 有趣的是,上面提到的文章说“在行级别审计日志很糟糕。在你了解完整情况的更高级别进行。”因此,对于这个特定问题,触发器可能就足够了,但不是一般审计跟踪的 解决方案。好书!
  • 感谢分享这篇文章。让我意识到我们的应用程序对软删除的使用之一仍然需要开发人员创建一个脚本来执行取消删除。对最终用户没有切实的好处。
【解决方案3】:

最通用的方法是创建另一个表来存储第一个表中的记录版本。然后,您可以从主表中删除所有数据。假设您需要对表 Person(PersonId, Name, Surname) 进行版本控制:

创建表人 ( PersonId INT, // PK CurrentPersonVersion INT // FK ); 创建表 PersonVersion ( PersonVersionId INT, // PK PersonID // FK Name VARCHAR, // 实际数据 Surname VARCHAR, // 实际数据 ChangeDate // 记录数据 ChangeAuthor // 记录数据 )

现在任何更改都需要插入新的 PersonVersion 并更新 CurrentPersonVersionID。

【讨论】:

    【解决方案4】:

    最好的方法是在数据库中设置写入审计表的触发器。

    【讨论】:

    • 根据您希望如何使用审计,所有被审计的表都可以写入一个表。这允许在框中打勾“是的,所有内容都经过审核”,而不会使事情变得过于复杂。如果你真的需要阅读它,那就不太好......
    • 一个审计表,非常糟糕的主意。由于所有内容都将转到该表,因此您将遇到许多用户的插入锁定问题。
    【解决方案5】:

    解决方案 1:SQL Server 变更数据捕获

    https://docs.microsoft.com/en-us/sql/relational-databases/track-changes/enable-and-disable-change-data-capture-sql-server?view=sql-server-2017

    首先您需要在数据库上启用变更数据捕获

    USE AdventureWorks2012
    GO  
    EXEC sys.sp_cdc_enable_db  
    GO  
    

    然后您可以使用fn_cdc_get_all_changes_fn_cdc_get_net_changes_ 查询更改。

    -- ========  
    -- Enumerate All Changes for Valid Range Template
    -- ========  
    USE AdventureWorks2012;  
    GO  
    
    DECLARE @from_lsn binary(10), @to_lsn binary(10);  
    SET @from_lsn = sys.fn_cdc_get_min_lsn('HR_Department');  
    SET @to_lsn   = sys.fn_cdc_get_max_lsn();  
    
    SELECT * FROM cdc.fn_cdc_get_all_changes_HR_Department  
    (@from_lsn, @to_lsn, N'all');  
    

    解决方案 2:SQL Server 数据库审计

    来源:https://www.dbaservices.com.au/how-to-configure-sql-server-auditing/

    启用数据库审计

    数据库审计需要有服务器审计(虽然不一定是服务器审计规范)。然而,数据库审计是在要审计的用户数据库中创建的,而不是在创建服务器审计的主数据库中创建的。数据库审计规范可以在数据库本身的 Security –> Database Audit Specifications 下找到。

    要创建数据库审核,您需要首先USE 数据库(以选择它),然后以下提供用于审核特定表的SELECTUPDATEDELETE 操作的示例语法在该数据库中;

    USE UserDatabase
    GO
    
    
    CREATE DATABASE AUDIT SPECIFICATION [User_Database_Audit_Specification]  
    FOR SERVER AUDIT [SQL_Server_Audit]
         ADD (SELECT , UPDATE , DELETE ON UserDatabase.dbo.Customer_DeliveryAddress BY dbo )
        ,ADD (SELECT , UPDATE , DELETE ON UserDatabase.dbo.DimCustomer_Email BY dbo )
        ,ADD (SELECT , UPDATE , DELETE ON UserDatabase.dbo.DimCustomer_Phone BY dbo )
    WITH (STATE = ON) ;   
    GO
    

    SELECTUPDATEDELETE 操作并不是您可以添加到审计规范中的唯一内容……

    +------------+-------------------------------------------------------------------+
    | Action     | Description                                                       |
    +------------+-------------------------------------------------------------------+
    | SELECT     | This event is raised whenever a SELECT is issued.                 |
    | UPDATE     | This event is raised whenever an UPDATE is issued.                | 
    | INSERT     | This event is raised whenever an INSERT is issued.                | 
    | DELETE     | This event is raised whenever a DELETE is issued.                 | 
    | EXECUTE    | This event is raised whenever an EXECUTE is issued.               | 
    | RECEIVE    | This event is raised whenever a RECEIVE is issued.                | 
    | REFERENCES | This event is raised whenever a REFERENCES permission is checked. | 
    +------------+-------------------------------------------------------------------+
    

    您可以记录的数据库事件的完整列表可在此处获得:

    https://docs.microsoft.com/en-us/sql/relational-databases/event-classes/security-audit-event-category-sql-server-profiler?view=sql-server-2017

    【讨论】:

    • 解决方案 1 和解决方案 2 是否需要支付许可证费用?
    【解决方案6】:

    我最近面临审计一些表的要求,我选择使用触发器。像其他人一样,我只想查看审计表中实际更改的那些字段的条目,但是,在更新表时,应用程序正在更新行中的所有字段,无论它们是否已更改,因此,检查是否字段已更新或对我没有任何帮助 - 他们都有!

    因此,我想要的是一种检查每个字段中的实际值以查看它是否已更改并且仅将其写入审计表(如果已更改)的方法。由于无法在任何地方找到解决此难题的任何方法,我想出了自己的解决方案,如下所示:

    CREATE TRIGGER [dbo].[MyTable_CREATE_AUDIT]
    ON [dbo].[MyTable]
    AFTER UPDATE
    
    AS
    
    INSERT INTO MyTable_Audit 
    (ItemID,LastModifiedBy,LastModifiedDate,field1,field2,field3,
    field4,field5,AuditDate)
    SELECT i.ItemID,i.LastModifiedBy,i.LastModifiedDate,
    
    field1 = 
      CASE i.field1
        WHEN d.field1 THEN NULL
        ELSE i.field1
      END,
    
    field2 = 
      CASE i.field2
        WHEN d.field2 THEN NULL
        ELSE i.field2
      END,
    
    field3 = 
      CASE i.field3
        WHEN d.field3 THEN NULL
        ELSE i.field3
      END,
    
    field4 = 
      CASE i.field4
        WHEN d.field4 THEN NULL
        ELSE i.field4
      END,  
    
    field5 = 
      CASE i.field5
        WHEN d.field5 THEN NULL
        ELSE i.field5
      END,
    
    GETDATE()
    
    FROM inserted i
    INNER JOIN deleted d
    ON i.ItemID = d.ItemID
    

    如您所见,我正在比较删除表和插入表中每个字段的值,如果它们不同,则仅将插入表中的字段值写入审计表,否则我只写 NULL。

    它当然对我有用。任何人都可以看到这种方法的任何问题吗?我的团队同时拥有应用程序和数据库,因此可能会出现诸如架构更改之类的曲线球。

    【讨论】:

    • 注意:同时编写 i.field1 和 d.field1 将使您不必查看上一行以找出上次的值。另外,我不确定这个方法是如何让我们确定是否写入空值是因为没有发生更改,还是因为值设置为空值。如果您的日志表有 2 行,并且在第 1 行中,field1 是“a”,而在第 2 行中,field1 是 NULL,那是因为 field1 仍然是“a”还是因为它已更新为 null.. 其他人需要考虑的事情采用这种方法
    【解决方案7】:

    除了触发器之外的另一种方法是,

    1. 对于要对其进行审计跟踪的每个表,有四列,UpdFlagDelFlagEffectiveDateTerminatedDate
    2. 以这样的方式对存储过程进行编码,以便在进行更新时将行的所有列数据传递到存储过程中,通过将TerminatedDate 设置为更新日期来更新行,并标记UpdFlag 并将日期时间放入列中
    3. 然后使用新数据创建一个新行(实际上已更新)。现在为EffectiveDateTerminatedDate 设置一个新日期,设置为最大日期。

    同样,如果您想删除该行,只需通过将DelFlag 标记为已设置、TerminatedDate 与现在的日期时间来更新该行。您实际上是在执行软删除,而不是实际的 sql 删除。

    这样,当您想要审核数据并显示更改跟踪时,您可以简单地过滤那些设置了UpdFlagEffectiveDateTerminatedDate 之间的行。同样,对于那些被删除的,您过滤那些设置了DelFlag 或介于EffectiveDateTerminatedDate 之间的那些。对于当前行,筛选同时设置了两个标志的行。优点是使用触发器时不必为审计创建另一个表!

    【讨论】:

    • 我再说一遍:如果不同的程序/交互式用户绕过这样的存储程序怎么办?通过触发器和适当的访问控制,您可以(取决于 DBMS)确保每个更改都经过审核。
    • @MaD70:确实如此,但是通过沿着正确的路径执行安全性并强制使用此类应用程序来执行此操作。绕过将被归类为安全漏洞的存储过程 - 比如为什么要绕过它会破坏审计的要点?感谢您的意见,它让我深思! ;) 保重!
    • Tommie,如果我要更新特定客户端的所有记录,我不会使用应用程序使用的一些存储过程并在循环中运行 100,000 次。我也不会将它用于数据导入,也不会有人试图进行欺诈或仅仅因为他们疯了而试图破坏数据。
    • @tommieb75:不客气。在现实生活中,没有人会记得(或留下)执行此类隐式策略的风险很高,因此最好将其放入数据库并让 DBMS 执行(适当的随附设计文档是也推荐)。
    【解决方案8】:

    我会去触发器路由,通过创建具有与更新表类似的结构的表,以及用于跟踪 ModifiedAt 等更改的附加列。然后添加将更改插入该表的更新触发器。 我发现它比应用程序代码中的所有内容更容易维护。当然,很多人在谈到“这个表正在改变”之类的问题时往往会忘记触发器;)干杯。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-16
      • 1970-01-01
      • 2012-03-04
      • 2011-02-01
      • 2023-03-04
      • 1970-01-01
      • 2012-12-31
      • 1970-01-01
      相关资源
      最近更新 更多