2012-08-09 324 views
17

我有2个表,Table-ATable-A-History如何在SQL Server中的历史记录表中存储历史记录

  • Table-A包含当前数据行。
  • Table-A-History包含历史数据

我想有我数据的最新行Table-A,并且Table-A-History含历史行。

我能想到的2种方式来实现:通过insert into select

  1. 每当一个新的数据行是可用的,从Table-A移动当前行Table-A-History和更新Table-A行最新数据( select into table

  2. 每当一个新的数据行是可用的,更新Table-A的行和插入排成Table-A-History

关于性能方法1或2更好?有没有更好的不同方法来完成这一点?

+2

您是否考虑过使用'Table-A'上的触发器为您创建'Table-A-History'行?确保它们设置为最后触发([sp_settriggerorder](http://msdn.microsoft.com/en-us/library/ms186762.aspx))。 – HABO 2012-08-09 19:59:44

+0

不,我没有。我会研究触发器。谢谢。 – Mausimo 2012-08-09 20:01:20

回答

18

记录更改是我通常在基表上使用触发器来记录日志表中的更改。日志表具有用于记录数据库用户,操作和日期/时间的附加列。

create trigger Table-A_LogDelete on dbo.Table-A 
    for delete 
as 
    declare @Now as DateTime = GetDate() 
    set nocount on 
    insert into Table-A-History 
    select SUser_SName(), 'delete-deleted', @Now, * 
     from deleted 
go 
exec sp_settriggerorder @triggername = 'Table-A_LogDelete', @order = 'last', @stmttype = 'delete' 
go 
create trigger Table-A_LogInsert on dbo.Table-A 
    for insert 
as 
    declare @Now as DateTime = GetDate() 
    set nocount on 
    insert into Table-A-History 
    select SUser_SName(), 'insert-inserted', @Now, * 
     from inserted 
go 
exec sp_settriggerorder @triggername = 'Table-A_LogInsert', @order = 'last', @stmttype = 'insert' 
go 
create trigger Table-A_LogUpdate on dbo.Table-A 
    for update 
as 
    declare @Now as DateTime = GetDate() 
    set nocount on 
    insert into Table-A-History 
    select SUser_SName(), 'update-deleted', @Now, * 
     from deleted 
    insert into Table-A-History 
    select SUser_SName(), 'update-inserted', @Now, * 
     from inserted 
go 
exec sp_settriggerorder @triggername = 'Table-A_LogUpdate', @order = 'last', @stmttype = 'update' 

记录触发器应该始终设置为最后触发。否则,后续触发器可能会回滚原始事务,但日志表已经更新。这是一个令人困惑的事态。

4

方法3如何:使Table-A成为针对Table-A-History的视图。插入Table-A-History并让适当的过滤逻辑生成Table-A。这样你只能插入一张表。

+1

我以为我应该分开这些表格,因为表格A将保存经常使用的〜10K条记录。历史记录表格会变得庞大而且使用量少得多。与表A相比5-10%的时间。性能方面,数据库频繁搜索10K记录会不会更好,如果我组合Table-A和Table-A-History – Mausimo 2012-08-09 19:59:43

+0

,可能可能不会,这取决于插入选择的比率。您还可以使Table-A成为索引视图(http://msdn.microsoft.com/zh-cn/library/dd171921%28v=sql.100%29.aspx)。这可能会彻底解决您的搜索问题。 – mwigdahl 2012-08-09 21:26:30

3

尽管消耗更多空间,但使用包含最新记录的历史记录表也可以节省编写报告和查看更改以及发生时间的方式。在我看来值得思考的东西。

就性能而言,我希望它们是相同的。但是,你当然不希望从非直方表中删除记录(选项1的“移动”),因为你在两个表之间使用了参照完整性,对吧?

+0

对,我会更新表A中的记录。表A历史记录表使用代理键+外键链接到Table-A – Mausimo 2012-08-09 20:04:16

2

我宁愿方法1
此外,我会还保持在历史表中当前记录过
这取决于需求。

3

选项1正常。 但是你有方法4太:)

  1. 插入新记录到表,

  2. 移动老记录用mysql调度归档定期基地台。您可以在最小负载时安排数据归档,例如在夜间。

+1

Opps,抱歉。但这个想法是一样的。如果你不想在白天表演时松动,只需在夜间进行;-) – Vahan 2012-08-09 20:41:36

+0

我认为这个问题不仅是关于插入,还包括UPDATE和DELETE。如果插入的行在同一天更新或删除,该怎么办?在这种情况下,不可能跟踪同一天完成的更改。 (如果问题仅涉及INSERT,则不需要审计表,因为没有数据将仅使用INSERT进行更改。) – beawolf 2016-10-28 08:47:40

36

基本上,您正在寻找跟踪/审计对表的更改,同时保持主表的大小不变。

有几种方法可以解决这个问题。下面讨论每种方式的缺点和优点。

1 - 使用触发器审计表。

如果您正在审计表(插入,更新,删除),请看我如何避免不需要的事务--SQL星期六幻灯片包含代码 - http://craftydba.com/?page_id=880。填充审计表的触发器可以保存来自多个表的信息(如果您选择),因为数据保存为XML。因此,如果需要,可以通过解析XML来取消删除操作。它追踪谁和做了什么改变。

(可选)您可以在其自己的文件组上拥有审核表。

Description: 
    Table Triggers For (Insert, Update, Delete) 
    Active table has current records. 
    Audit (history) table for non-active records. 

Pros: 
    Active table has smaller # of records. 
    Index in active table is small. 
    Change is quickly reported in audit table. 
    Tells you what change was made (ins, del, upd) 

Cons: 
    Have to join two tables to do historical reporting. 
    Does not track schema changes. 

2 - 有效的约会记录

如果你是永远不会从审核表中清除数据,为什么不标记行作为删除,但保持它永远?许多像人一样的系统使用有效的约会来显示记录是否不再有效。在BI世界中,这被称为类型2维表(缓慢变化的维度)。请参阅数据仓库学院文章。 http://www.bidw.org/datawarehousing/scd-type-2/每条记录​​都有开始日期和结束日期。

所有活动记录的结束日期都为null。

Description: 
    Table Triggers For (Insert, Update, Delete) 
    Main table has both active and historical records. 

Pros: 
    Historical reporting is easy. 
    Change is quickly shown in main table. 

Cons: 
    Main table has a large # of records. 
    Index of main table is large. 
    Both active & history records in same filegroup. 
    Does not tell you what change was made (ins, del, upd) 
    Does not track schema changes. 

3 - 更改数据捕获(企业功能)。

Micorsoft SQL Server 2008引入了更改数据捕获功能。虽然这跟踪数据更改(CDC),事实上使用LOG读取器,但它缺少诸如谁和做什么改变之类的事情。 MSDN详细信息 - http://technet.microsoft.com/en-us/library/bb522489(v=sql.105).aspx

此解决方案依赖于正在运行的CDC作业。 SQL代理的任何问题都会导致数据显示延迟。

请参阅更改数据捕获表。 http://technet.microsoft.com/en-us/library/bb500353(v=sql.105).aspx

Description: 
    Enable change data capture 

Pros: 
    Do not need to add triggers or tables to capture data. 
    Tells you what change was made (ins, del, upd) the _$operation field in 
    <user_defined_table_CT> 
    Tracks schema changes.  

Cons: 
    Only available in enterprise version. 
    Since it reads the log after the fact, time delay in data showing up. 
    The CDC tables do not track who or what made the change. 
    Disabling CDC removes the tables (not nice)! 
    Need to decode and use the _$update_mask to figure out what columns changed. 

4 - 变化跟踪功能(所有版本)。

Micorsoft SQL Server 2008引入了更改跟踪功能。与CDC不同,它具有所有版本;然而,它带有一些TSQL函数,你必须调用它来找出发生了什么。

它旨在通过应用程序与SQL服务器同步一个数据源。 TechNet上有一个完整的同步框架。

http://msdn.microsoft.com/en-us/library/bb933874.aspx http://msdn.microsoft.com/en-us/library/bb933994.aspx http://technet.microsoft.com/en-us/library/bb934145(v=sql.105).aspx

与疾病预防控制中心,您可以指定最后在数据库中清除之前多久变化。另外,插入和删除不记录数据。更新只记录更改的字段。

由于您正在将SQL服务器源同步到另一个目标,所以此工作正常。 除非您编写定期作业以找出更改,否则这不利于审核。

您仍然需要在某处存储该信息。

Description: 
    Enable change tracking 

Cons: 
    Not a good auditing solution 

前三种解决方案将适用于您的审计。我喜欢第一个解决方案,因为我在我的环境中广泛使用它。

真诚

约翰

代码段从演示(汽车数据库)

-- 
-- 7 - Auditing data changes (table for DML trigger) 
-- 


-- Delete existing table 
IF OBJECT_ID('[AUDIT].[LOG_TABLE_CHANGES]') IS NOT NULL 
    DROP TABLE [AUDIT].[LOG_TABLE_CHANGES] 
GO 


-- Add the table 
CREATE TABLE [AUDIT].[LOG_TABLE_CHANGES] 
(
    [CHG_ID] [numeric](18, 0) IDENTITY(1,1) NOT NULL, 
    [CHG_DATE] [datetime] NOT NULL, 
    [CHG_TYPE] [varchar](20) NOT NULL, 
    [CHG_BY] [nvarchar](256) NOT NULL, 
    [APP_NAME] [nvarchar](128) NOT NULL, 
    [HOST_NAME] [nvarchar](128) NOT NULL, 
    [SCHEMA_NAME] [sysname] NOT NULL, 
    [OBJECT_NAME] [sysname] NOT NULL, 
    [XML_RECSET] [xml] NULL, 
CONSTRAINT [PK_LTC_CHG_ID] PRIMARY KEY CLUSTERED ([CHG_ID] ASC) 
) ON [PRIMARY] 
GO 

-- Add defaults for key information 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_DATE] DEFAULT (getdate()) FOR [CHG_DATE]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_TYPE] DEFAULT ('') FOR [CHG_TYPE]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_BY] DEFAULT (coalesce(suser_sname(),'?')) FOR [CHG_BY]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_APP_NAME] DEFAULT (coalesce(app_name(),'?')) FOR [APP_NAME]; 
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_HOST_NAME] DEFAULT (coalesce(host_name(),'?')) FOR [HOST_NAME]; 
GO 



-- 
-- 8 - Make DML trigger to capture changes 
-- 


-- Delete existing trigger 
IF OBJECT_ID('[ACTIVE].[TRG_FLUID_DATA]') IS NOT NULL 
    DROP TRIGGER [ACTIVE].[TRG_FLUID_DATA] 
GO 

-- Add trigger to log all changes 
CREATE TRIGGER [ACTIVE].[TRG_FLUID_DATA] ON [ACTIVE].[CARS_BY_COUNTRY] 
    FOR INSERT, UPDATE, DELETE AS 
BEGIN 

    -- Detect inserts 
    IF EXISTS (select * from inserted) AND NOT EXISTS (select * from deleted) 
    BEGIN 
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET]) 
    SELECT 'INSERT', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM inserted as Record for xml auto, elements , root('RecordSet'), type) 
    RETURN; 
    END 

    -- Detect deletes 
    IF EXISTS (select * from deleted) AND NOT EXISTS (select * from inserted) 
    BEGIN 
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET]) 
    SELECT 'DELETE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type) 
    RETURN; 
    END 

    -- Update inserts 
    IF EXISTS (select * from inserted) AND EXISTS (select * from deleted) 
    BEGIN 
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET]) 
    SELECT 'UPDATE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type) 
    RETURN; 
    END 

END; 
GO 



-- 
-- 9 - Test DML trigger by updating, deleting and inserting data 
-- 

-- Execute an update 
UPDATE [ACTIVE].[CARS_BY_COUNTRY] 
SET COUNTRY_NAME = 'Czech Republic' 
WHERE COUNTRY_ID = 8 
GO 

-- Remove all data 
DELETE FROM [ACTIVE].[CARS_BY_COUNTRY]; 
GO 

-- Execute the load 
EXECUTE [ACTIVE].[USP_LOAD_CARS_BY_COUNTRY]; 
GO 

-- Show the audit trail 
SELECT * FROM [AUDIT].[LOG_TABLE_CHANGES] 
GO 

-- Disable the trigger 
ALTER TABLE [ACTIVE].[CARS_BY_COUNTRY] DISABLE TRIGGER [TRG_FLUID_DATA]; 

**看审计表的&感觉**

enter image description here

+0

非常棒的阅读,感谢您的洞察!我只想验证我是否理解了您的代码段。您只有1个“日志表变更”表,它将存储来自每个其他表的记录,并且这些表中的实际记录存储在XML中?这样,你只有一个审计表? – Mausimo 2013-10-02 19:51:44

+0

很酷的部分是它是你2。假设你的公司做税收,你的数据保存期为7年。您可能希望在chg_date上使用多个审计表分区。查看我关于数据仓库技术的介绍。另一方面,如果您为商业销售冰淇淋,您可能会将收据保留两年。然后一张桌子可能会很好。 – 2013-10-02 20:31:36

+0

触发器的“缺陷”不应该包括增加的插入/更新/删除时间吗?我觉得这是正确决定的一部分,这里是平衡写入速度和查询速度。 – Daniel 2017-09-29 15:01:09

8

最新版本的SQL服务器(2016+和Azure)具有临时表格,可以提供所需的确切功能,作为第一类功能。 https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables

微软的某人可能读过此页面。 :)

+1

感谢您的加入。这个问题实际上是在很久以前。巧合的是,我正在研究一个具有类似要求的新项目,而我正在使用Azure。我将研究Temporal-Tables。干杯! – Mausimo 2017-05-02 14:53:23