9

我的任务是开发一个跟踪数据库更改的解决方案。

对于我需要捕获的更新:

  • 更新日期
  • 旧值
  • 新价值
  • 受影响的领域
  • 做改变的人
  • 记录编号
  • 表记录在

对于删除:

  • 删除日期
  • 删除的人
  • 已删除记录的标题/描述/ID。我正在跟踪所有更改的表都有标题或描述字段。我想在删除记录之前捕获它。
  • 表记录在

对于插入:

  • 插入日期
  • 做改变的人
  • 记录编号
  • 表记录在

我想了几种方法来做到这一点:

  • 我正在使用存储过程进行任何更新/删除/插入。我会创建一个通用的“跟踪”表。它将有足够的字段来捕获所有数据。然后,我会在每个存储过程中添加另一行,以实现“将记录插入跟踪表”的效果。
    • 缺点:所有更新/删除/插入都混杂在同一张表中
    • 很多 NULL 字段
    • 如何跟踪批量更新/删除/插入?<---- 这可能不是问题。我真的没有在应用程序中做任何这样的事情。
    • 如何捕获进行更新的用户。数据库只看到一个帐户。
    • 编辑大量现有代码进行编辑。
  • 最后,我可以创建一个在更新/删除/插入之后调用的触发器。许多与第一个解决方案相同的缺点,除了:我必须编辑尽可能多的代码。我不确定如何跟踪更新。看起来没有办法使用触发器来查看最近更新的记录。

我正在使用 asp.net、C#、sql server 2005、iis6、windows 2003。我没有预算,很遗憾我买不到任何东西来帮助我解决这个问题。

感谢您的回答!

4

8 回答 8

5

我不想回避这个问题,我知道你没有预算,但最简单的解决方案是升级到 SQL Server 2008。它内置了这个功能。我认为至少应该为遇到此问题的其他人提及这一点,即使您自己不能使用它。

(在 SQL 2008 的可部署版本中,此功能仅在 Enterprise 中可用。)

于 2008-11-18T20:09:51.690 回答
5

由于多种原因,触发器不会包含您需要的所有信息 - 但没有用户 ID 是关键。

我会说你在正确的轨道上使用一个通用的 sp 插入任何进行更改的地方。如果您正在为您的接口标准化 sp,那么您就处于领先地位 - 很难潜入未跟踪的更改。

将此视为相当于会计应用程序中的审计跟踪 - 这是日志 - 记录每笔交易的单个表。他们不会为存款、取款、调整等实施单独的日记帐,这是相同的原则。

于 2008-11-18T20:16:08.290 回答
3

我建议您在每个表中使用 2 列。名称rowhistoryIsDeleted并且数据类型将是 xml 和 bit。 永远不要删除行,总是使用标志 IsDeleted 现在使用更新触发器。我会给你举个例子,我有一个叫做 Page 的表

    CREATE TABLE te_Page([Id] [int] IDENTITY(1,1) NOT NULL, [Name] [varchar](200) NOT NULL, [Description] [varchar](200) NULL,[CreatedBy] [uniqueidentifier] NULL, [CreatedDate] [datetime] NOT NULL, [UpdatedBy] [uniqueidentifier] NULL, [UpdatedDate] [datetime] NULL, [IsDeleted] [bit] NULL, [RowHistory] [xml] NULL, CONSTRAINT [PK_tm_Page] PRIMARY KEY CLUSTERED ([Id] ASC )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY] ) ON [PRIMARY]

现在创建表格后,您需要做的就是复制粘贴下面的代码,您的任务就完成了 Page 表格。它将开始记录同一行中行的历史记录,该行与旧值和新值一起更新。

                ALTER Trigger [dbo].[Trg_Te_Page]    
        On [dbo].[te_Page]                
        After Update                
        As                 
        --If @@rowcount = 0 Or Update(RowHistory)    
        --Return    

        Declare @xml NVARCHAR(MAX)     
        Declare @currentxml NVARCHAR(MAX)   
        Declare @node NVARCHAR(MAX)    
        Declare @ishistoryexists XML    

        Declare @FormLineAttributeValueId int  

        -- new Values  
        Declare @new_Name varchar(200)  
        Declare @new_Description varchar(200)  

        Declare @new_CreatedBy UNIQUEIDENTIFIER    
        Declare @new_CreatedDate DATETIME    
        Declare @new_UpdatedBy UNIQUEIDENTIFIER    
        Declare @new_UpdatedDate DATETIME    
        Declare @new_IsDeleted BIT  

        --old values  
        Declare @old_Name varchar(200)  
        Declare @old_Description varchar(200)  

        Declare @old_CreatedBy UNIQUEIDENTIFIER    
        Declare @old_CreatedDate DATETIME    
        Declare @old_UpdatedBy UNIQUEIDENTIFIER    
        Declare @old_UpdatedDate DATETIME    
        Declare @old_IsDeleted BIT  


        -- declare temp fmId  
        Declare @fmId int  
        -- declare cursor  
        DECLARE curFormId cursor   
        FOR select Id from INSERTED   
        -- open cursor       
        OPEN curFormId  
        -- fetch row  
        FETCH NEXT FROM curFormId INTO @fmId  

        WHILE @@FETCH_STATUS  = 0   
        BEGIN   

        Select   
        @FormLineAttributeValueId = Id,   
        @old_Name = Name,  
        @old_Description = [Description],  

        @old_CreatedBy = CreatedBy,    
        @old_CreatedDate =CreatedDate,  
        @old_UpdatedBy =UpdatedBy,    
        @old_UpdatedDate =UpdatedDate,  
        @old_IsDeleted  = IsDeleted,  
        @currentxml = cast(RowHistory as NVARCHAR(MAX))  
        From DELETED where Id=@fmId  



        Select      
        @new_Name = Name,  
        @new_Description = [Description],  

        @new_CreatedBy = CreatedBy,    
        @new_CreatedDate =CreatedDate,  
        @new_UpdatedBy =UpdatedBy,    
        @new_UpdatedDate =UpdatedDate,  
        @new_IsDeleted  = IsDeleted  
        From INSERTED where Id=@fmId  

        set @old_Name = Replace(@old_Name,'&','&amp;')
        set @old_Name = Replace(@old_Name,'>','&gt;')  
        set @old_Name = Replace(@old_Name,'<','&lt;')     
        set @old_Name = Replace(@old_Name,'"','&quot;')
        set @old_Name = Replace(@old_Name,'''','&apos;')          

        set @new_Name = Replace(@new_Name,'&','&amp;')      
        set @new_Name = Replace(@new_Name,'>','&gt;')  
        set @new_Name = Replace(@new_Name,'<','&lt;')     
        set @new_Name = Replace(@new_Name,'"','&quot;')
        set @new_Name = Replace(@new_Name,'''','&apos;') 

        set @old_Description = Replace(@old_Description,'&','&amp;')
        set @old_Description = Replace(@old_Description,'>','&gt;')  
        set @old_Description = Replace(@old_Description,'<','&lt;')     
        set @old_Description = Replace(@old_Description,'"','&quot;')
        set @old_Description = Replace(@old_Description,'''','&apos;')          

        set @new_Description = Replace(@new_Description,'&','&amp;')      
        set @new_Description = Replace(@new_Description,'>','&gt;')  
        set @new_Description = Replace(@new_Description,'<','&lt;')     
        set @new_Description = Replace(@new_Description,'"','&quot;')
        set @new_Description = Replace(@new_Description,'''','&apos;')   

        set @xml = ''     

        BEGIN      

        -- for Name  
        If ltrim(rtrim(IsNull(@new_Name,''))) != ltrim(rtrim(IsNull(@old_Name,'')))    
        set @xml = @xml + '<ColumnInfo ColumnName="Name" OldValue="'+ @old_Name + '" NewValue="' + @new_Name + '"/>'    

        -- for Description  
        If ltrim(rtrim(IsNull(@new_Description,''))) != ltrim(rtrim(IsNull(@old_Description,'')))    
        set @xml = @xml + '<ColumnInfo ColumnName="Description" OldValue="'+ @old_Description + '" NewValue="' + @new_Description + '"/>'    

        -- CreatedDate     
        If IsNull(@new_CreatedDate,'') != IsNull(@old_CreatedDate,'')  
        set @xml = @xml + '<ColumnInfo ColumnName="CreatedDate" OldValue="'+ cast(isnull(@old_CreatedDate,'') as varchar(100)) + '" NewValue="' + cast(isnull(@new_CreatedDate,'') as varchar(100)) + '"/>'    

        -- CreatedBy     
        If cast(IsNull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar (36)) != cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000')as varchar(36))    
        set @xml = @xml + '<ColumnInfo ColumnName="CreatedBy" OldValue="'+ cast(IsNull(@old_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(isnull(@new_CreatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
        '"/>'    

        -- UpdatedDate       
        If IsNull(@new_UpdatedDate,'') != IsNull(@old_UpdatedDate,'')    
        set @xml = @xml + '<ColumnInfo ColumnName="UpdatedDate" OldValue="'+ cast(IsNull(@old_UpdatedDate,'') as varchar(100)) + '" NewValue="' + cast(IsNull(@new_UpdatedDate,'') as varchar(100)) + '"/>'    

        -- UpdatedBy     
        If cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) != cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))    
        set @xml = @xml + '<ColumnInfo ColumnName="UpdatedBy" OldValue="'+ cast(IsNull(@old_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36)) + '" NewValue="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(36))+
        '"/>'    

        -- IsDeleted  
        If cast(IsNull(@new_IsDeleted,'') as varchar(10)) != cast(IsNull(@old_IsDeleted,'') as varchar(10))    
        set @xml = @xml + '<ColumnInfo ColumnName="IsDeleted" OldValue="'+ cast(IsNull(@old_IsDeleted,'') as varchar(10)) + '" NewValue="' + cast(IsNull(@new_IsDeleted,'') as varchar(10)) + '" />'    

        END    

        Set @xml = '<RowInfo TableName="te_Page" UpdatedBy="' + cast(IsNull(@new_UpdatedBy,'00000000-0000-0000-0000-000000000000') as varchar(50)) +  '" UpdatedDate="' + Convert(Varchar(20),GetDate()) + '">' + @xml + '</RowInfo>'    
        Select @ishistoryexists = RowHistory From DELETED     

        --print @ishistoryexists  


        If @ishistoryexists is null    
        Begin     
        Set @xml = '<History>' + @xml + '</History>'      
        Update te_Page    
        Set    
        RowHistory = @xml    
        Where     
        Id = @FormLineAttributeValueId    

        End    

        Else    
        Begin     
        set @xml = REPLACE(@currentxml, '<History>', '<History>' + @xml)  
        Update te_Page  
        Set  
        RowHistory = @xml  
        Where   
        Id = @FormLineAttributeValueId     
        End  


        FETCH NEXT FROM curFormId INTO @fmId  
        END   


        CLOSE curFormId  
        DEALLOCATE curFormId  

现在,每当您执行任何更新时,您的数据都将存储在rowhistory列中

于 2012-07-19T11:09:43.990 回答
1

我见过这种处理的一种方法(虽然我不推荐它,老实说)是通过存储过程来处理它,将用户 ID/用户名/任何东西作为参数传递。存储过程将调用日志记录过程,该过程将相关详细信息写入中央日志表。

不过,这就是它有点古怪的地方......

对于 INSERT/UPDATE,一旦 INSERT/UPDATE 成功完成,相关行就会作为 XML 数据存储在表中。对于 DELETE,行是在 DELETE 运行之前存储的(但实际上,他们可以从 DELETE 语句的输出中获取它——至少在 SQL Server 2005 中是这样)。

如果我没记错的话,该表只有几列:UserID、日志记录的 DateTime、Transaction Type (I/U/D)、包含相关行的 XML 数据、表名和主键值(主要用于快速搜索他们想要什么记录)。

给猫剥皮的方法有很多,但是...

我的建议是保持简单。如果/当您需要时,稍后将其展开。

如果您有能力这样做,请将用户锁定为只能通过存储过程对表执行可操作的语句,然后从那里处理日志记录(无论您想要什么)。

于 2008-11-18T20:32:41.813 回答
0

我们构建了自己的,只需要将用户和 pc 传递到每个添加/更新存储过程中。那么只需获取原始记录并填充变量并将它们与传入的变量进行比较并将数据记录到我们的表中即可。对于删除,我们只有原始表的副本 + 时间戳字段,因此该记录永远不会真正删除,并且可以在我们需要的任何时候恢复(显然删除例程检查 FK 关系等)。

添加/更新日志表看起来像 datetime、table_name、column_name、record_id、old_value、new_value、user_id、computer

我们从不插入空值,因此我们将它们转换为空字符串,新条目在 old_value 列中用“{new entry}”标记。record_id 由尽可能多的键列组成,以唯一标识该单个记录( field1 + '.' + field2 + ... )

于 2008-11-18T20:36:16.987 回答
0

首先,在所有表中,您应该至少将这些列添加到数据列 DateCreated、UserCreated、DateModified、UserModified。可能您可能想要添加“状态”或“最后操作”列,这样您就不会真正删除只是将其设置为已删除/插入/更新状态的行。接下来,您可以创建一个“历史表”,它是第一个表的精确副本。然后在任何更新或删除时让触发器将 Deleted 表条目复制到 History 表中,同时更改 DateModified、UserModified 和 Status 字段。

于 2008-11-19T14:25:02.553 回答
0

我在 SQL Server 中有一个设置,我们将使用视图来访问我们的数据,这将使用 INSTEAD OF 触发器处理插入、更新和删除。

例如:视图上的INSTEAD OF DELETE触发器会将基础表中的记录标记为已删除,并且视图被过滤为不显示已删除的记录。

在所有触发器中,我们更新了修改日期和用户名。问题是记录的数据库用户名与最终的应用程序用户名不同。

视图需要是模式绑定才能工作。

于 2009-02-12T21:24:01.980 回答
0

关于记录更改数据库的用户:您可以根据需要为数据库创建尽可能多的 SQL 用户,如果您使用会话和对程序/脚本的受限/注册访问,您可以使用该信息来启动不同的数据库连接设置(即用户名) , 在对 DB 进行任何操作之前。

至少这对于 PHP-wise 脚本应该是可行的,但对于 asp.net 我可能是错误的。

于 2012-06-27T10:27:38.127 回答