2012-08-09 376 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