SqlServer 變更資料捕獲(CDC)
變更資料捕獲(Change Data Capture ,簡稱 CDC)記錄 SQL Server 表的插入、更新和刪除活動。使用變更資料捕獲可以更有效跟蹤表物件DML歷史操作,對 ETL 等資料轉移也非常有用。
變更資料捕獲適用版本:
SQL Server 2008 以上的 Enterprise Edition、Developer Edition 和 Evaluation Edition
變更資料捕獲原理:
變更資料捕獲的更改資料來源為 SQL Server 事務日誌。當對錶啟用變更資料捕獲時,系統將生成一個與該表結構類似的副本。當對源表進行插入、更新和刪除 時,在事務日誌會記錄相關操作資訊。變更資料捕獲代理使用非同步程序讀取事務日誌,將相關操作結果應用到副本表(捕獲例項表)中,這樣就完成了對源表操作的記錄跟蹤。
變更資料捕獲例項:
在資料庫 MyDatabase 中建立測試表:
-- 建立測試表 USE MyDatabase GO CREATE TABLE CDC_Test ( id int not null, name varchar(50) not null, insertDate datetime not null, value numeric(14,4) not null ) GO ALTER TABLE CDC_Test ADD CONSTRAINT PK_CDC_Test PRIMARY KEY CLUSTERED (id) GO CREATE NONCLUSTERED INDEX IX_CDC_Test_NAME ON CDC_Test(name) GO CREATE UNIQUE NONCLUSTERED INDEX IX_CDC_Test_insertDate ON CDC_Test(insertDate) GO
檢視資料庫或表是否啟用了cdc:
-- 檢視資料庫是否啟用cdc
SELECT name,is_cdc_enabled FROM sys.databases WHERE is_cdc_enabled = 1
-- 檢視當前資料庫表是否啟用cdc
SELECT name,is_tracked_by_cdc FROM sys.tables WHERE is_tracked_by_cdc = 1
對當前資料庫啟用cdc :
-- 對當前資料庫啟用cdc
USE MyDatabase
GO
EXECUTE sys.sp_cdc_enable_db;
GO
可能出現以下錯誤及解決辦法:
/* 訊息 22830,級別 16,狀態 1,過程 sp_cdc_enable_db_internal,第 186 行 無法更新元資料來指示已對資料庫 MyDatabase 啟用了變更資料捕獲。執行命令 'SetCDCTracked(Value = 1)' 時失敗。 返回的錯誤為 15404: '無法獲取有關 Windows NT 組/使用者 'KK\administrator' 的資訊,錯誤程式碼 0x54b。'。 請使用此操作和錯誤來確定失敗的原因並重新提交請求。 訊息 266,級別 16,狀態 2,過程 sp_cdc_enable_db_internal,第 0 行 EXECUTE 後的事務計數指示 BEGIN 和 COMMIT 語句的數目不匹配。上一計數 = 0,當前計數 = 1。 訊息 266,級別 16,狀態 2,過程 sp_cdc_enable_db,第 0 行 EXECUTE 後的事務計數指示 BEGIN 和 COMMIT 語句的數目不匹配。上一計數 = 0,當前計數 = 1。 訊息 3998,級別 16,狀態 1,第 1 行 在批處理結束時檢測到不可提交的事務。該事務將回滾。 */ -- 原因是資料庫所有者為Windows使用者,改為“sa” EXEC dbo.sp_changedbowner @loginame = N'sa', @map = false GO --依賴別名已刪除
啟動資料庫cdc後,接著對指定源表啟用 cdc :
-- 接著對指定源表啟用cdc
EXEC sys.sp_cdc_enable_table
@source_schema= 'dbo', --源表架構
@source_name = 'CDC_Test', --源表
@role_name = 'CDC_Role' --角色(將自動建立)
GO
--作業 'cdc.MyDatabase_capture' 已成功啟動。
--作業 'cdc.MyDatabase_cleanup' 已成功啟動。
建立完成後,將看到資料庫中建立了以下物件:
-- 也可以使用指令碼檢視跟蹤表的資訊
EXEC sys.sp_cdc_help_change_data_capture
GO
EXEC sys.sp_cdc_help_change_data_capture 'dbo', 'CDC_Test'
GO
下面介紹系統表中,各表的主要資訊:
【cdc.captured_columns】
select * from cdc.captured_columns
EXEC sys.sp_cdc_get_captured_columns N'dbo_CDC_Test';
【cdc.change_tables】
select *fromcdc.change_tables
每對一個源表啟用變更資料捕獲時,
EXEC sys.sp_cdc_help_change_data_capture
EXEC sys.sp_cdc_help_change_data_capture @source_schema='dbo',@source_name='CDC_Test'
【cdc.index_columns】
select *fromcdc.index_columns
對源表中的主鍵列或者啟用變更資料捕獲時指定的唯一索引列(指定索引優於主鍵)記錄一行,變更資料捕獲使用這些索引列來唯一標識源表中的行。預設情況下,
EXEC sys.sp_cdc_help_change_data_capture @source_schema = 'dbo' ,@source_name = 'CDC_Test'
或者使用該儲存過程檢視(如上圖)
【cdc.ddl_history】
select *fromcdc.ddl_history
針對啟用了變更資料捕獲的表所做的每一資料定義語言(DDL)更改返回一行。可以使用此表來確定源表發生DDL
EXEC sys.sp_cdc_get_ddl_history @capture_instance='dbo_CDC_Test'
--測試 DDL 操作後再檢視
ALTER TABLE CDC_Test ADD info VARCHAR(20)
ALTER TABLE CDC_Test DROP COLUMN info
【cdc.<capture_instance>_CT】
select *fromcdc.dbo_CDC_Test_CT
捕獲例項表:這是最重要的表,該表就是記錄源表的所有DML操作記錄。每個表對應一個例項表,命名方式為“架構名_表名_CT”
應用於源表的每個插入或刪除操作在更改表中各佔一行。插入操作生成的行的資料列包含插入後的列值。刪除操作生成的行的資料列包含刪除前的列值。更新操作需要兩行資料:一行用於標識更新前的列值,另一行用於標識更新後的列值。
現在對源表進行插入、更新、刪除後,檢視該跟蹤例項表:
-- 進行相關操作
insert into CDC_Test(id,name,insertDate,value)
select 1,'kk',GETDATE(),55
go
update CDC_Test set name = 'hh',value = 50 where name = 'kk'
go
delete from CDC_Test where id = 1
go
select * from cdc.dbo_CDC_Test_CT
__$start_lsn :與相應更改的提交事務關聯的日誌序列號 (LSN)
__$end_lsn : (在 SQL Server 2008中,此列始終為 NULL)
__$seqval :對事務內的行更改順序
__$operation :源表DML操作
1 = 刪除
2 = 插入
3 = 更新(舊值)
4 = 更新(新值)
__$update_mask :基於更改表的列序號的位掩碼,用於標識那些發生更改的列
再測試 DDL 對更改表的影響:
-- 進行相關操作
INSERT INTO CDC_Test(id,name,insertDate,value)SELECT 2,'mm',GETDATE(),0
ALTER TABLE CDC_Test ADD info VARCHAR(20)--新增1新列
INSERT INTO CDC_Test(id,name,insertDate,value) SELECT 2,'mm',GETDATE(),0
ALTER TABLE CDC_Test DROP COLUMN value--刪除1列
INSERT INTO CDC_Test(id,name,insertDate) SELECT 3,'hh',GETDATE()
SELECT * FROM cdc.dbo_CDC_Test_CT --檢視更改表
結果總結:
1. 新新增的列(如 info),在更改表中不會新增。但仍可進行跟蹤記錄操作,只是不在表cdc.captured_columns 中的列則不跟蹤記錄。
2. 刪除了列(如 value),更改表中則標識為null 。即時再新增建立原來的欄位,也無效。
增加或者刪除一列後,沒有記錄跟蹤,這種情況增麼辦?
一種方法是:
1. 增刪某欄位
2. 再對同一個表啟用另一個變更資料捕獲(新的變更表為源表當前的結構)
3.再根據 ID/時間/唯一鍵 等從新的跟蹤表取資料(非實時獲取資料情況,如定期轉移資料等)
4. 如覺得每次呼叫都更改表名,可以使用檢視,呼叫檢視查詢更改表,檢視只要更改對應的表就行。
捕獲例項表中的一些約束:
Timestamp/ rowversion 列的資料型別被定義為 binary(8)
Identity 列的資料型別被定義為 int 或 bigint
對於 LOB 資料型別 varchar(max)、nvarchar(max)、varbinary(max)、image、text、ntext 和 xml,如果LOB列被更新,則在捕獲表才記錄更新前的值,否則(即時更新其他列)更新前的值為null。這樣節省了空間。
Truncate table 將無法對啟用跟蹤的表使用
SWITCH PARTITION 部分行將不會被捕獲
【cdc.lsn_time_mapping】
select * from cdc.lsn_time_mapping
當捕獲程序提交每批新的更改資料時,
DECLARE @begin_time datetime, @end_time datetime, @begin_lsn binary(10), @end_lsn binary(10);
SET @begin_time = '2015-05-16 00:00:00.000';
SET @end_time = '2015-05-17 00:00:00.000';
SELECT @begin_lsn = sys.fn_cdc_map_time_to_lsn('smallest greater than or equal', @begin_time);
SELECT @end_lsn = sys.fn_cdc_map_time_to_lsn('largest less than', @end_time);
--SELECT @begin_lsn,@end_lsn
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_CDC_Test( @begin_lsn , @end_lsn , 'all update old' )
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_CDC_Test( @begin_lsn , @end_lsn , 'all' )
SELECT * FROM cdc.fn_cdc_get_net_changes_dbo_CDC_Test( @begin_lsn , @end_lsn , 'all' )
--如果有連續的空結果集(例如,當捕獲作業正在連續執行時),則最後一個現有行中的 empty_scan_count 將遞增
-- 為變更資料捕獲日誌掃描會話中遇到的每個錯誤返回一行
select * from sys.dm_cdc_errors
-- 針對當前資料庫中的每個日誌掃描會話返回一行。返回的最後一行表示當前會話。
select * from sys.dm_cdc_log_scan_sessions
-- 空掃描的會話
SELECT * from sys.dm_cdc_log_scan_sessions where empty_scan_count <> 0
-- 返回最近進行的會話的平均滯後時間
SELECT latency FROM sys.dm_cdc_log_scan_sessions WHERE session_id = 0
-- 返回最近會話的平均吞吐量
SELECT command_count/duration AS [Throughput] FROM sys.dm_cdc_log_scan_sessions WHERE session_id = 0
-- 檢視捕獲作業或清除作業的資訊
-- SELECT * FROM msdb.dbo.sysjobs
SELECT * FROM msdb.dbo.cdc_jobs
EXEC sys.sp_cdc_help_jobs
--對作業的更改
EXEC sys.sp_cdc_change_job
@job_type = 'capture'
,@maxtrans = 1000 --每個掃描迴圈可以處理的最多事務數
,@maxscans = 10 --為了從日誌中提取所有行而要執行的最大掃描迴圈次數
,@continuous = 1 --連續執行最多處理(max_trans * max_scans) 個事務
,@pollinginterval = 5
EXEC sys.sp_cdc_change_job
@job_type = 'cleanup'
,@retention = 4320 --更改行將在更改表中保留的分鐘數
,@threshold = 5000 --清除時可以使用一條語句刪除的刪除項的最大數量
--更改後需重啟作業
EXEC sys.sp_cdc_stop_job @job_type = N'capture';
EXEC sys.sp_cdc_stop_job @job_type = N'capture';
EXEC sys.sp_cdc_start_job @job_type = N'cleanup';
EXEC sys.sp_cdc_start_job @job_type = N'cleanup';
在可更新訂閱的複製中,跟蹤表記錄有些不同:
在釋出啟用cdc:
若建立了釋出,則cdc的capture作業將不建立,共同使用日誌讀取器代理(使用sp_replcmds 讀取日誌)
在釋出庫更新一行記錄,則在跟蹤表記錄6行(更新了3次)
在訂閱庫更新一行記錄,同步到釋出,則在跟蹤表記錄2行(更新1次)
在訂閱啟用cdc:
在釋出庫更新一行記錄,同步到訂閱,則在跟蹤表記錄6行(更新了3次)
在訂閱庫更新一行記錄,則在跟蹤表記錄4行(更新2次)
主要原因是:可更新訂閱的校驗列 msrepl_tran_version 更新多次。
最後一項,禁用(刪除)變更資料捕獲:
-- 對錶禁用變更資料捕獲
USE MyDatabase;
GO
EXEC sys.sp_cdc_disable_table
@source_schema = N'dbo',
@source_name = N'CDC_Test',
@capture_instance = N'dbo_CDC_Test'
GO
-- 對資料庫禁用變更資料捕獲
USE MyDatabase;
GO
EXECUTE sys.sp_cdc_disable_db;
GO
--執行完成後,相關的表、函式、使用者、角色、架構、作業都會完全刪除!