http://www.cnblogs.com/downmoon/archive/2012/04/10/2439462.html
本文主要介紹SQL Server中記錄數據變更的四個方法:觸發器、Output子句、變更數據捕獲(Change Data Capture 即CDC)功能、同步更改跟蹤。其中後兩個爲SQL Server 2008所新增。
一、觸發器
在SQL Server的早期版本中,如果要記錄某個表或視圖的Insert/Update/Delete操作,我們可以藉助觸發器(Trigger)(http://msdn.microsoft.com/zh-cn/library/ms189799.aspx),這在數據量較小的情況下往往是有效的方式之一,其中後觸發器(After Trigger)只能跟蹤表的三個操作中的任意組合,而前觸發器(Instead Of trigger)可以處理表和視圖的更新(即使普通的Update View語句在某些列不明確的情況下報錯)。我們看兩個例子:
準備基礎數據:
USE testDb2 GO --創建兩個測試表 IF NOT OBJECT_ID('DepartDemo') IS NULL DROP TABLE [DepartDemo] GO IF NOT OBJECT_ID('DepartChangeLogs') IS NULL DROP TABLE [DepartChangeLogs] GO --測試表 CREATE TABLE [dbo].[DepartDemo]( [DID] [int] IDENTITY(101,1) NOT NULL PRIMARY KEY, [DName] [nvarchar](200) NULL, [DCode] [nvarchar](500) NULL, [Manager] [nvarchar](50) NULL, [ParentID] [int] NOT NULL DEFAULT ((0)), [AddUser] [nvarchar](50) NULL, [AddTime] [datetime] NULL, [ModUser] [nvarchar](50) NULL, [ModTime] [datetime] NULL, [CurState] [smallint] NOT NULL DEFAULT ((0)), [Remark] [nvarchar](500) NULL, [F1] [int] NOT NULL DEFAULT ((0)), [F2] [nvarchar](300) NULL ) GO --記錄日誌表 CREATE TABLE [DepartChangeLogs] ([LogID] [bigint] IDENTITY(1001,1) NOT NULL PRIMARY KEY, [DID] [int] NOT NULL, [DName] [nvarchar](200) NULL, [DCode] [nvarchar](500) NULL, [Manager] [nvarchar](50) NULL, [ParentID] [int] NOT NULL DEFAULT ((0)), [AddUser] [nvarchar](50) NULL, [AddTime] [datetime] NULL, [ModUser] [nvarchar](50) NULL, [ModTime] [datetime] NULL, [CurState] [smallint] NOT NULL DEFAULT ((0)), [Remark] [nvarchar](500) NULL, [F1] [int] NOT NULL DEFAULT ((0)), [F2] [nvarchar](300) NULL, [LogTime] DateTime Default(Getdate()) Not Null, [InsOrUpd] char not null ) GO
創建觸發器:
/******* 創建一個After DML觸發器 ******/ /********* [email protected] 邀月***************/ CREATE TRIGGER dbo.tri_LogDepartDemo ON [dbo].[DepartDemo] AFTER INSERT, Delete /************此處使用update與「Insert,Delete」效果是一樣的,邀月注 **********/ AS SET NOCOUNT ON --屏蔽觸發器發送「受影響的行數」給應用程序 -- Inserted rows INSERT [DepartChangeLogs] (DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], LogTime, InsOrUPD) SELECT DISTINCT DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], GETDATE(), 'I' FROM inserted i -- Deleted rows INSERT [DepartChangeLogs] (DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], LogTime, InsOrUPD) SELECT DISTINCT DID,[DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2], GETDATE(), 'D' FROM deleted d GO INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) VALUES (N'國家統計局房產審計一科', N'0', N'胡不歸', 0, N'DeomUser', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'專業評估全國房價,爲老百姓謀福祉', 0, N'') GO ----該Update不會被觸發器記錄,但Update會生效 UPDATE departDemo SET [Manager]='任我行' WHERE DID=101 GO DELETE FROM departDemo where DID=101 GO SELECT * FROM [DepartChangeLogs]
統計效果:
如果你覺得觸發器過於浪費,你可以試着根據某些字段以縮小觸發器的範圍
/********* 使用DML觸發器記錄特定列的修改 ***/ /********* [email protected] 邀月***************/ CREATE TRIGGER dbo.[tri_LogDepartDemo2] ON [dbo].[DepartDemo] AFTER Update AS IF Update([Manager]) Begin print '該部門主管實行終身任免制,不得中途更改!' Rollback ----回滾Update操作 End GO UPDATE departDemo SET [Manager]='任我行' WHERE DID=101 GO
執行結果:
但觸發器的缺陷也是顯而易見的,使用觸發器請注意以下幾點:
1、觸發器通常很隱蔽,換句話說,易忘記,特別在檢查性能和邏輯問題時。
2、長時間運行的觸發器會嚴重減慢數據操作,特別是在數據頻繁修改的數據庫中。
3、不記錄日誌的更新不會引起DML觸發器的觸發(如WRITETEXT、Trunacte table及批量插入操作)。
4、約束通常比觸發器運行更快。
5、處理某些邏輯時,存儲過程通常比觸發器要更易維護和管理。
6、不允許在觸發器中使用Select返回結果集。
關於觸發器的更多內容,請看MSDN(http://msdn.microsoft.com/zh-cn/library/ms189799.aspx)
二、使用Output子句
官方解釋:OutPut子句(http://technet.microsoft.com/zh-cn/library/ms177564.aspx)返回受 INSERT、UPDATE、DELETE 或 MERGE 語句影響的各行中的信息,或返回基於受這些語句影響的各行的表達式。 這些結果可以返回到處理應用程序,以供在確認消息、存檔以及其他類似的應用程序要求中使用。 也可以將這些結果插入表或表變量。 另外,您可以捕獲嵌入的 INSERT、UPDATE、DELETE 或 MERGE 語句中 OUTPUT 子句的結果,然後將這些結果插入目標表或視圖。
舉例:
/********* 使用Output記錄表記錄的修改 *****/ /********* [email protected] 邀月***************/ ----刪除前面的觸發器 Drop TRIGGER dbo.[tri_LogDepartDemo] DROP TRIGGER dbo.[tri_LogDepartDemo2] INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) OUTPUT Inserted.*,getdate(),'I' ---注意這行是新增的 INTO DepartChangeLogs ---注意這行是新增的 VALUES (N'發改委', N'0', N'向問天', 0, N'DeomUser', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'油價,我說了算', 0, N'') GO SELECT * FROM [DepartChangeLogs]
注意:
1、從OUTPUT 中返回的列反映 INSERT、UPDATE 或 DELETE 語句完成之後但在觸發器執行之前的數據。
2、SQL Server 並不保證由使用 OUTPUT 子句的 DML 語句處理和返回行的順序。
3、與觸發器相比,OutPut子句可以直接處理Merge語句。
以上兩種方法各有千秋,在合適的情況下采取合適的方法纔是明智的選擇,令人驚喜的是,SQL Server 2008起,爲我們提供了更爲強大的內建的方法-變更數據捕獲(CDC,http://msdn.microsoft.com/zh-cn/library/bb500244%28v=sql.100%29.aspx)和更改跟蹤,下面我們隆重介紹它們。
三、使用「變更數據捕獲」(CDC)功能
SQL Server 2008提供了內建的方法變更數據捕獲(Change Data Capture 即CDC)以實現異步跟蹤用戶表的數據修改,而且這一功能擁有最小的性能開銷。可以用於其他數據源的持續更新,例如將OLTP數據庫中的數據變更遷移到數據倉庫數據庫。
要使用CDC功能,首先我們得在數據庫中啓用該功能。在此我們沿用上例中使用的數據庫Testdb2
/**************異步跟蹤數據更新演示*************/ /************* [email protected] 邀月***************/ use master GO IF EXISTS (SELECT [name] FROM sys.databases WHERE name = 'TestDb2') drop DATABASE TestDb2 Go CREATE DATABASE TestDb2 GO --查看是否啓用CDC SELECT is_cdc_enabled FROM sys.databases WHERE name = 'TestDb2' USE TestDb2 GO ----啓用當前數據庫的CDC功能 EXEC sys.sp_cdc_enable_db GO
如果報15517錯誤,請換用其他owner,邀月注
SELECT is_cdc_enabled FROM sys.databases WHERE name = 'TestDb2' /* is_cdc_enabled 1 */ USE testDb2 GO CREATE TABLE [dbo].[DepartDemo]( [DID] [int] IDENTITY(101,1) NOT NULL PRIMARY KEY, [DName] [nvarchar](200) NULL, [DCode] [nvarchar](500) NULL, [Manager] [nvarchar](50) NULL, [ParentID] [int] NOT NULL DEFAULT ((0)), [AddUser] [nvarchar](50) NULL, [AddTime] [datetime] NULL, [ModUser] [nvarchar](50) NULL, [ModTime] [datetime] NULL, [CurState] [smallint] NOT NULL DEFAULT ((0)), [Remark] [nvarchar](500) NULL, [F1] [int] NOT NULL DEFAULT ((0)), [F2] [nvarchar](300) NULL ) GO /********************************** 需要啓用SQL Server Agent服務,否則會報錯,邀月注 SQLServerAgent is not currently running so it cannot be notified of this action. ***********************************/ /****** 捕獲所有的行變更,只返回行的淨變更,其他默認 *******/ EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', @source_name = 'DepartDemo', @role_name = NULL, @capture_instance = NULL, @supports_net_changes = 1, @index_name = NULL, @captured_column_list = NULL, @filegroup_name = default
注意此時,SQL Server 自啓動了兩個job,一個捕獲,一個清除,注意清除是默認凌晨2點,清除72小時以上的數據。如果同一數據庫的表中CDC已經啓用,不會重建job。
/* Job 'cdc.TestDb2_capture' started successfully. Job 'cdc.TestDb2_cleanup' started successfully. */ --確認表已經被跟蹤 SELECT is_tracked_by_cdc FROM sys.tables WHERE name = 'DepartDemo' and schema_id = SCHEMA_ID('dbo') /* is_tracked_by_cdc 1 */ --確認 EXEC sys.sp_cdc_help_change_data_capture 'dbo', 'DepartDemo'
可以看到,SQL Server 增加了一個表[cdc].[dbo_DepartDemo_CT]
相比源表多了個字段:
[__$start_lsn]
,[__$end_lsn]
,[__$seqval]
,[__$operation]
,[__$update_mask]
不建議直接查詢該表,而應該使用下面的技巧:
USE TestDb2 GO INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) VALUES (N'銀監會', N'0', N'雲中鶴', 0, N'DemoUser1', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'監管匯率', 0, N'') INSERT [dbo].[DepartDemo] ([DName], [DCode], [Manager], [ParentID], [AddUser], [AddTime], [ModUser], [ModTime], [CurState], [Remark], [F1], [F2]) VALUES (N'統計局', N'0', N'神算子', 0, N'DemoUser2', CAST(0x00009DF7017B6F96 AS DateTime), N'', CAST(0x0000000000000000 AS DateTime), 1, N'統計數據', 0, N'') GO UPDATE [dbo].[DepartDemo] SET Manager='段正淳' WHERE DID =101 DELETE [dbo].[DepartDemo] WHERE DID = 102
要查詢變更,我們需要藉助大名鼎鼎的日誌序列號(Log Sequence Numbers)即LSN(http://msdn.microsoft.com/zh-cn/library/ms190411%28v=sql.100%29.aspx)來實現LSN級別的跟蹤數據變更。 下面示例中sys.fn_cdc_map_time_to_lsn(http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx)用於LSN轉換爲時間。
/******* 使用LSN 查看CDC記錄 *********/ --http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx SELECT sys.fn_cdc_map_time_to_lsn ( 'smallest greater than or equal' , '2012-04-09 16:09:30') GO UPDATE [dbo].[DepartDemo] SET Manager='段正淳' WHERE DID =101 DELETE [dbo].[DepartDemo] WHERE DID = 102
要查詢變更,我們需要藉助大名鼎鼎的日誌序列號(Log Sequence Numbers)即LSN(http://msdn.microsoft.com/zh-cn/library/ms190411%28v=sql.100%29.aspx)來實現LSN級別的跟蹤數據變更。 下面示例中sys.fn_cdc_map_time_to_lsn(http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx)用於LSN轉換爲時間。
/******* 使用LSN 查看CDC記錄 *********/ --http://msdn.microsoft.com/zh-cn/library/bb500137%28v=sql.100%29.aspx SELECT sys.fn_cdc_map_time_to_lsn ( 'smallest greater than or equal' , '2012-04-09 16:09:30') as BeginLSN /* BeginLSN 0x0000002C000000AA0003 */ SELECT sys.fn_cdc_map_time_to_lsn ( 'largest less than or equal' , '2012-04-09 23:59:59') as EndLSN /* EndLSN 0x0000002C000001C20005 */ /**************查看所有CDC記錄*************/ /************* [email protected] 邀月***************/ DECLARE @FromLSN varbinar