介紹
SQL Server 2008引入了CDC(Change Data Capture),它能記錄:
1. 哪些數據行發生了改變
2. 數據行變更的歷史記錄,而不僅僅是最終值。
跟CT(Change Tracking)相比,它通過作業實現異步變更跟蹤(像事務復制),而CT是同步實現的。因此它對性能的影響較輕並且不會影響事務。
典型應用是在提取、傳輸和加載數據到其它數據源,就像圖中的數據倉庫。
實現
微軟建議CDC結合快照快照隔離級別使用,可以避免讀取變更數據與變更數據寫入時的讀寫阻塞。
需要注意:快照隔離級別會有額外的開銷,特別是Tempdb(所有的數據更改都會被版本化存到tempdb)。
use master go create database CDCTest go alter database CDCTest set allow_snapshot_isolation on go --enable CDC on database CDCTest use CDCTest go exec sys.sp_cdc_enable_db go
啟用CDC之后會新增一個叫CDC的Schema和一系列的系統表、SP和View。官方建議不要直接查詢系統表而是使用對應的系統SP/FN來獲取CDC數據。
系統對象 |
說明 |
建議使用的對象 |
cdc.captured_columns |
為在捕獲實例中跟蹤的每一列返回一行 |
|
cdc.change_tables |
為數據庫中的每個更改表返回一行 |
|
cdc.ddl_history |
針對啟用了變更數據捕獲的表所做的每一數據定義語言 (DDL) 更改返回一行 |
|
cdc.lsn_time_mapping |
為每個在更改表中存在行的事務返回一行 |
sys.fn_cdc_map_lsn_to_time (Transact-SQL) , sys.fn_cdc_map_time_to_lsn (Transact-SQL) |
cdc.index_column |
為與更改表關聯的每一索引列返回一行 |
|
msdb.dbo.cdc_jobs |
存儲用於捕獲和清除作業的變更數據捕獲配置參數 |
NA |
cdc.<capture_instance>_CT |
對源表啟用變更數據捕獲時創建的更改表。 該表為對源表執行的每個插入和刪除操作返回一行,為對源表執行的每個更新操作返回兩行.capture_instance格式=SchameName_TableName |
創建測試表並對期啟用CDC。使用sys.sp_cdc_enable_table 對表啟用CDC。
--Create a test table for CDC use CDCTest GO create table tb(ID int primary key ,name varchar(20),weight decimal(10,2)); go EXECUTE sys.sp_cdc_enable_table @source_schema = N'dbo' , @source_name = N'tb' , @role_name = null; GO
如果源表是數據庫中第一個要啟用變更數據捕獲的表,並且數據庫不存在事務發布,則 sys.sp_cdc_enable_table 還將為數據庫創建捕獲和清理作業。 它將 sys.tables 目錄視圖中的 is_tracked_by_cdc 列設置為 1。
對應的跟蹤表cdc.dbo_tb_CT包含了源表所有的變更數據。它包含原來所有的列和5個新的列,結構如圖:
驗證
當在源表中操行數據更改操作,表cdc.dbo_tb_CT會記錄下來。試一下:
為什么沒有數據呢?因為之前介紹過了,CDC是靠作業來捕獲變更數據的,我的Agent還沒有運行。
手動啟用后,就有數據了。
結果列的含義:
列名 |
數據類型 |
說明 |
__$start_lsn |
binary(10) |
更改提交的LSN。在同一事務中提交的更改將共享同一個提交 LSN 值。 |
__$seqval |
binary(10) |
一個事務內可能有多個更改發生,這個值用於對它們進行排序。 |
__$operation |
int |
更改操作的類型: 1 = 刪除 2 = 插入 3 = 更新(捕獲的列值是執行更新操作前的值)。 4 = 更新(捕獲的列值是執行更新操作后的值)。 |
__$update_mask |
varbinary(128) |
位掩碼,源表中被CDC跟蹤的每一列對應一個位。如果 __$operation = 1 或 2,該值將所有已定義的位設置為 1。如果 __$operation = 3 或 4,則只有那些對應已更改列的位設置為 1。 |
現在再插入一行,並更新它,然后再刪除ID=1的行。再查看結果:
簡單說明一下跟蹤的查詢結果:總共5行,第一行和第二行是插入數據,第三行和第四行是更新前后的數據,第五行是刪除數據。操作類型由_$operation值可得知。
簡單應用
前文中創建的tb表,記錄了每個人的姓名和體重變化信息。另外某一個數據庫(表tb_rs),它是體重變化趨勢報表的數據源。它每天同步一次數據,更新自己的數據。怎么用CDC來實現這個需求呢?
CDC中記錄了start_lsn,如果能知道tb_rs上次同步完成時,tb中被同步的最大LSN。那下次同步時,只需要同步tb表中大於此LSN的變更記錄即可。
問題就簡單:獲取上次同步完成tb的最大LSN,獲取大於此LSN的所有變更記錄,更新tb_rs。
- 由sys.fn_cdc_map_time_to_lsn可以將時間映射到對應的LSN,時間就是前一天。
- 由cdc.fn_cdc_get_net_changes_<capture_instance>能得到一天內的所有的凈變更記錄。
- 由變更記錄自定義同步邏輯和語句。
insert into tb values(1,'Ken',70.2),(3,'Joe',66),(4,'Rose',50) update tb set weight=70 where ID=3; delete from tb where name='Rose'; go DECLARE @begin_time datetime, @end_time datetime, @begin_lsn binary(10), @end_lsn binary(10); --get the interval select @begin_time=GETDATE()-1,@end_time=GETDATE(); --map the time to LSN of the CDC table tb select @begin_lsn = sys.fn_cdc_map_time_to_lsn('smallest greater than or equal', @begin_time), @end_lsn = sys.fn_cdc_map_time_to_lsn('largest less than or equal', @end_time); --get the net changes within the specified LSNs SELECT * FROM cdc.fn_cdc_get_net_changes_dbo_tb(@begin_lsn, @end_lsn, 'all');
居然沒有Rose的記錄?Joe的信息被更新過,怎么才一條記錄?
這是因為這里得到是凈變更行,也就是最終結果的意思。新增然后又刪除,不影響最終結果,所以沒有。多次更新同一行的某一列數據,只返回最后更新的結果。
得到這個結果,我們就可以根據__$operation和實際數據定義同步數據的邏輯了。比如:
--generate sync statements SELECT (case __$operation when 2 then 'insert into tb_rs values ('+cast(ID as varchar(2))+', '+Name+', '+cast(weight as varchar(10))+')' when 4 then 'update tb_rs set name='+name+',weight='+cast(weight as varchar(10))+' where ID='++cast(ID as varchar(2)) END) FROM cdc.fn_cdc_get_net_changes_dbo_tb(@begin_lsn, @end_lsn, 'all');
對於更新過的行,同步數據時,我想要先判斷出列是否被更改過和被更改的時間。更改過的列才需要被同步,而不是所有列同步一次。以name為例:
DECLARE @begin_time datetime, @end_time datetime, @begin_lsn binary(10), @end_lsn binary(10); --get the interval select @begin_time=GETDATE()-1,@end_time=GETDATE(); --map the time to LSN of the CDC table tb select @begin_lsn = sys.fn_cdc_map_time_to_lsn('smallest greater than or equal', @begin_time), @end_lsn = sys.fn_cdc_map_time_to_lsn('largest less than or equal', @end_time); --get the all changes within the specified LSNs SELECT *, (Case sys.fn_cdc_has_column_changed('dbo_tb','name',__$update_mask) when 1 then 'Yes' when 0 then 'No' End) as isNameUpdated, sys.fn_cdc_map_lsn_to_time(__$start_lsn) as updateTime FROM cdc.fn_cdc_get_all_changes_dbo_tb(@begin_lsn, @end_lsn, 'all') where __$operation in(3,4); go
CDC不僅能記錄DML操作,還能記錄DDL操作。查詢cdc.ddl_history。
但有一點要格外注意:新增的列,能被CDC DDL跟蹤到,但是新列的數據變更卻不能被CDC跟蹤到。如果需要跟蹤它,先禁用表上的CDC,再啟用即可。
CDC Agent Job
在指定的數據庫中首次啟用CDC,並且不存在事務復制,則會創建capture和cleanup兩個作業:
capture作業是用於掃描日志文件,把變更記錄寫到變更表中。調用sp_MScdc_capture_job來實現,可以根據當前庫的實際事務吞吐量來設置掃描參數和掃描間隔,使得在性能開銷和跟蹤需求間達到合理平衡。
cleanup作業是清理變更變表中的數據,默認三天的數據。
所以合理設定cleanup的間隔是非常重要的。
這兩個作業的相關的配置存儲在msdb.dbo.cdc_jobs中。當前的默認配置如圖:
總結
1. CDC使用方便,易於配置,能與同步抽取等應用結合使用。
2. CDC能滿足大多數對數據審計的要求,但不能告訴你“誰”更改了數據。
3. 雖說CDC是異步的,對應性能影響小,但還是會增加開銷,特別是IO讀寫和容量方面的。開啟CDC,每次更改,都至少會額外增加一次數據文件寫和日志文件寫操作。