1. 程式人生 > >SqlServer 變更資料捕獲(CDC)

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

--執行完成後,相關的表、函式、使用者、角色、架構、作業都會完全刪除!