SQL Server 2008中的CDC(Change Data Capture)功能使用及釋疑
關鍵詞:CDC,數據庫審計
原文:http://www.cnblogs.com/chenxizhang/archive/2011/08/10/2133408.html
SQL Server 2008中的CDC(Change Data Capture)功能使用及釋疑
CDC(Change Data Capture:變更數據捕獲)這個功能是SQL Server 2008企業版的功能,它提供了一種新的機制,對表格數據的更新進行跟蹤,在數據倉庫的建設過程中,通過這種技術,可以簡化從業務數據庫導入數據的復雜度。
之前我有過兩篇文章介紹,最近因為又在和有關客戶介紹這方面的應用。發現之前的例子不是那么完整和清楚,特此再整理一篇出來,給大家參考
一、什么是CDC?
變更數據捕獲(Change Data Capture ,簡稱 CDC)記錄 SQL Server 表的插入、更新和刪除活動。SQLServer的操作會寫日志,這也是CDC捕獲數據的來源。
開啟cdc的源表在插入、更新和刪除活動時會插入數據到日志表中。cdc通過捕獲進程將變更數據捕獲到變更表中,通過cdc提供的查詢函數,我們可以捕獲這部分數據。
二、開啟CDC
2.1、開啟CDC的必要條件
-
sqlserver 2008 以上版本
-
需要開啟代理服務(作業)
-
磁盤要有足夠的空間,保存日志文件
-
表必須要有主鍵或者是唯一索引
2.2、開啟數據庫CDC
1、 在需要開啟cdc的數據庫上執行腳本如下:
if exists(select 1 from sys.databases where name='db_name' and is_cdc_enabled=0) begin exec sys.sp_cdc_enable_db end
2、查詢數據庫的cdc開啟狀態
select is_cdc_enabled from sys.databases where name='db_name'
查詢結果為“1”,表示開啟成功。
2.3、開啟表CDC
*注意:表中必須有主鍵或者唯一索引
1、添加次要數據文件組及文件
數據庫右鍵“屬性” >> “文件組”>> ”添加”
“文件” >> “添加”
2、執行以下腳本,開啟表cdc
--CDC是數據庫文件組的名稱 IF EXISTS(SELECT 1 FROM sys.tables WHERE name='table_name' AND is_tracked_by_cdc = 0) BEGIN EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', -- source_schema @source_name = 'table_name', -- table_name @capture_instance = NULL, -- capture_instance @supports_net_changes = 1, -- supports_net_changes @role_name = NULL, -- role_name @index_name = NULL, -- index_name @captured_column_list = NULL, -- captured_column_list @filegroup_name = 'CDC' -- filegroup_name END
3、查看表cdc開啟狀態
SELECT is_tracked_by_cdc FROM sys.tables WHERE name='table_name'
查詢結果為“1”,表示開啟成功。
三、使用CDC
開啟cdc后會在數據庫中生成以下文件,開啟數據庫GY_DB,開啟表VW_GHZDK
下面我們會對部分表和函數進行說明
系統表:
cdc.change_tables:表開啟cdc后會插入一條數據到這張表中,記錄表一些基本信息
cdc.captured_columns:開啟cdc后的表,會記錄它們的字段信息到這張表中
cdc.VW_GHZDK_CT(cdc.表名_CT):記錄VW_GHZDK表中所有變更的數據,
字段“__$operation”為“1”代表刪除,“2”代表插入,“3”執行更新操作前的值,“4”執行更新操作后的值。字段“__$start_lsn”由於更改是來源於數據庫的事務日志,所以這里會保存其事務日志的開始序列號(LSN)
函數:
cdc.fn_cdc_get_all_changes_dbo_VW_GHZDK:針對在指定日志序列號 (LSN) 范圍內應用到源表的每項更改均返回一行。如果源行在該間隔內有多項更改,則每項更改都會表示在返回的結果集中
cdc.fn_cdc_get_net_changes_dbo_VW_GHZDK:針對指定 LSN 范圍內每個已更改的源行返回一個凈更改行。也就是說,如果在 LSN 范圍內源行具有多項更改,則該函數將返回反映該行最終內容的單一行
sys.fn_cdc_map_time_to_lsn:為指定的時間返回 cdc.lsn_time_mapping 系統表中 start_lsn 列中的日志序列號 (LSN) 值。可以使用此函數系統地將日期時間范圍映射到基於 LSN 的范圍,以供變更數據捕獲枚舉函數 cdc.fn_cdc_get_all_changes_<capture_instance> 和 cdc.fn_cdc_get_net_changes_<capture_instance> 返回此范圍內的數據更改。
四、最佳實踐:案例演示
-----------------------------------
1. 准備一個數據庫,里面准備一個表,Orders
2. 啟用數據庫級別的CDC選項
--在數據庫級別啟用CDC功能
EXEC sys.sp_cdc_enable_db
這個命令執行完之后,會在系統表里面添加6個表格
3.在需要做數據捕獲的表上面啟用CDC選項
EXEC sys.sp_cdc_enable_table @source_schema='dbo', @source_name='Orders', @capture_instance='Orders', @supports_net_changes=0, @role_name=null
【備注】關於這個存儲過程的具體用法和有關參數的含義,請參考
http://msdn.microsoft.com/en-us/library/bb522475.aspx
執行之后,會有如下的輸出消息
這個提示的意思是說,要啟動SQL Server Agent。因為CDC功能是要通過一個兩個作業來自動化完成的
與此同時,執行上面的命令還將在系統表中添加一個表格
還會添加一個函數
4.插入或者更新數據測試CDC功能
--插入或者更新數據測試CDC功能 INSERT Orders(CustomerID) VALUES('Microsoft'); INSERT Orders(CustomerID) VALUES('Google'); UPDATE Orders SET CustomerID='Yahoo' WHERE OrderID=1 DELETE FROM Orders WHERE OrderID=2
這個范例插入兩行數據,緊接着又對第一行更新,然后還刪除了第二行,所以最終只有一行數據
那么,我們來看看CDC做了什么事情呢?
SELECT * FROM cdc.Orders_CT
我們可以來解釋一下上面結果的含義
__$operation=2的情況,表示新增
__$operation=3或者4,表示更新,3表示舊值,4表示新值
__$operation=1的情況,表示刪除
很好理解,不是嗎?
但是,我們一般都是需要按照時間范圍進行檢索,對吧,所以,需要使用下面的語法進行查詢
--按照時間范圍查詢CDC結果 DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) DECLARE @start_time DATETIME = '2011-8-10 00:00:00' DECLARE @end_time DATETIME ='2011-8-11 00:00:00' SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time) SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time) SELECT * FROM cdc.fn_cdc_get_all_changes_Orders(@from_lsn,@end_lsn,'all')
關於sys.fn_cdc_map_time_to_lsn這個函數,請參考
http://msdn.microsoft.com/en-us/library/bb500137.aspx
查詢的結果如下
如果需要包含更新操作的舊值,則可以以下的語法
DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) DECLARE @start_time DATETIME = '2011-8-10 00:00:00' DECLARE @end_time DATETIME ='2011-8-11 00:00:00' SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time) SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time) SELECT * FROM cdc.fn_cdc_get_all_changes_Orders(@from_lsn,@end_lsn,'all update old')
通常,為了方便起見,我們會將這個查詢定義為一個存儲過程,如下
--定義存儲過程來進行查詢 CREATE PROC GetOrdersCDCResult(@start_time DATETIME,@end_time DATETIME) AS BEGIN DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time) SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time) SELECT * FROM cdc.fn_cdc_get_all_changes_Orders(@from_lsn,@end_lsn,'all') END
然后,每次需要用的時候,就直接調用即可
--執行存儲過程 EXEC GetOrdersCDCResult '2011-8-10','2011-8-11'
5.結合SSIS實現事實表的增量更新
下面展示了一個SSIS 包的設計,這里面讀取CDC的數據,先進行一些查找,然后按照__$operation的值拆分成為三個操作,分別進行插入,更新和刪除,這樣就可以實現對事實表的增量更新
【5】代碼匯總
USE SampleDatabase GO --在數據庫級別啟用CDC功能 EXEC sys.sp_cdc_enable_db --在需要做數據捕獲的表格上面啟用CDC功能 EXEC sys.sp_cdc_enable_table
@source_schema='dbo',
@source_name='Orders',
@capture_instance='Orders',
@supports_net_changes=0,
@role_name=null --插入或者更新數據測試CDC功能 INSERT Orders(CustomerID) VALUES('Microsoft'); INSERT Orders(CustomerID) VALUES('Google'); UPDATE Orders SET CustomerID='Yahoo' WHERE OrderID=1 DELETE FROM Orders WHERE OrderID=2 --查詢CDC的結果 SELECT * FROM cdc.Orders_CT --按照時間范圍查詢CDC結果 DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) DECLARE @start_time DATETIME = '2011-8-10 00:00:00' DECLARE @end_time DATETIME ='2011-8-11 00:00:00' SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time) SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time) SELECT * FROM cdc.fn_cdc_get_all_changes_Orders(@from_lsn,@end_lsn,'all') --定義存儲過程來進行查詢 CREATE PROC GetOrdersCDCResult(@start_time DATETIME,@end_time DATETIME) AS BEGIN DECLARE @from_lsn BINARY(10),@end_lsn BINARY(10) SELECT @from_lsn=sys.fn_cdc_map_time_to_lsn('smallest greater than or equal',@start_time) SELECT @end_lsn=sys.fn_cdc_map_time_to_lsn(' largest less than or equal',@end_time) SELECT * FROM cdc.fn_cdc_get_all_changes_Orders(@from_lsn,@end_lsn,'all') END --執行存儲過程 EXEC GetOrdersCDCResult '2011-8-10','2011-8-11'
【6】CDC的注意事項
【6.1】不能truncate
truncate 了~會提示不能使用的喲~
【6.2】修改表結構的坑
測試數據
EXEC sys.sp_cdc_enable_db CREATE TABLE AAA2 ( ID INT PRIMARY KEY, Col1 NVARCHAR(50), Col2 INT ) EXEC sys.sp_cdc_enable_table @source_schema = 'dbo', @source_name = 'AAA2' INSERT INTO dbo.AAA2( ID, Col1,Col2 ) VALUES ( 1, 'pp',34 ),( 2, 'bb',234 ),( 3, 'cc',12 ) UPDATE dbo.AAA2 SET Col1 = 'dd' WHERE ID = 3 DELETE FROM dbo.AAA2 WHERE ID = 2
SELECT * FROM cdc.dbo_AAA2_CT
__$start_lsn __$end_lsn __$seqval __$operation __$update_mask ID Col1 Col2 ---------------------- ------------- ---------------------- ------------ -------------------- ---- ------ ----------- 0x0000015B0001378F0019 NULL 0x0000015B0001378F0016 2 0x07 1 pp 34 0x0000015B0001378F0019 NULL 0x0000015B0001378F0017 2 0x07 2 bb 234 0x0000015B0001378F0019 NULL 0x0000015B0001378F0018 2 0x07 3 cc 12 0x0000015B000137A50003 NULL 0x0000015B000137A50002 3 0x02 3 cc 12 0x0000015B000137A50003 NULL 0x0000015B000137A50002 4 0x02 3 dd 12 0x0000015B000137AB0005 NULL 0x0000015B000137AB0002 1 0x07 2 bb 234
栗子1 ,刪除了一列Col2 然后再插入2條數據,然后還是會存在Col2的列(因為不需要改結構嘛╮(╯_╰)╭),然后跟蹤的時候把值設置成空
ALTER TABLE dbo.AAA2 DROP COLUMN Col2 INSERT INTO dbo.AAA2 ( ID, Col1 ) VALUES ( 4, N'DD'),( 5, N'EE') __$start_lsn __$seqval __$operation __$update_mask ID Col1 Col2 0x0000015B000138F40004 0x0000015B000138F40002 2 0x07 4 DD NULL 0x0000015B000138F40004 0x0000015B000138F40003 2 0x07 5 EE NULL
栗子2 ,然后我腦抽的重新把Col2 加進去~然而我改成了字符串類型,然后從新插入數據 ,Col2 沒值啊!!!那也正常,因為這是就結構,雖然名字一樣,但是ColumnID已經不一樣了啊!所以追蹤不到是很正常的。
ALTER TABLE dbo.AAA2 ADD Col2 NVARCHAR(50) INSERT INTO dbo.AAA2 ( ID, Col1, Col2 ) VALUES ( 6, -- ID - int N'jj', -- Col1 - nvarchar(50) 'jjj' -- Col2 - int ) __$start_lsn __$seqval __$operation __$update_mask ID Col1 Col2 0x0000015B000138F40004 0x0000015B000138F40002 2 0x07 4 DD NULL 0x0000015B000138F40004 0x0000015B000138F40003 2 0x07 5 EE NULL 0x0000015B000139640003 0x0000015B000139640002 2 0x07 6 jj NULL
栗子3 ,那我更新總可以了吧!當更新的是元結構有的列,是可以更新成功的,但是如果是新列做了改動,則捕獲不了,正常嘛(想想既然可以給你配置可跟蹤的列,那么不存在這個列表里面的列發生變化不捕捉,就是這個道理羅~)
UPDATE dbo.AAA2 SET Col2 = 'ee' WHERE ID = 4 UPDATE dbo.AAA2 SET Col1 = 'III' WHERE ID = 6 __$start_lsn __$seqval __$operation __$update_mask ID Col1 Col2 0x0000015B000139C00003 0x0000015B000139C00002 4 0x02 6 III NULL
那該如何處理這個問題呢~
從心做一個捕獲實例~從心做一個捕獲實例~從心做一個捕獲實例 重要的事情說3便 ╮(╯_╰)╭。也只有這樣羅