1 概述
“更改跟蹤”和“變更數據捕獲”捕獲和記錄用戶表的DML更改(插入、更新和刪除操作),為某些有特殊需求的應用程序服務。
1.1 更改跟蹤
更改跟蹤捕獲表的數據行更改這一行為,但不會捕獲更改的具體數據。捕獲的結果包含表的主鍵及相關的跟蹤信息(例如更改的操作類型、更新操作影響的列等)。
應用程序可以利用這個捕獲的結果來確定表的最新更新,並可以關聯原始來來獲取最新的數據。
1.2 變更數據捕獲
變更數據捕獲使用異步進程讀取事務日志,獲取DML更改實際數據做為數據捕獲的結果。在捕獲結果中,還包含更改相關的一些信息(例如更改的操作類型、更新操作影響的列等)。
應用程序可以從捕獲結果中獲取DML更改的全部數據,而無需查詢數據變更的原始表。
1.3 比較更改跟蹤和變更數據捕獲
變更數據捕獲與更改跟蹤都是記錄表的DML操作
變更數據捕獲可把操作數據的歷史值保存下來;更改跟蹤捕獲更改了表行這一事實,但不會捕獲更改的數據。
變更數據捕獲使用異步進程捕獲,該進程掃描事務日志;更改跟蹤同步跟蹤DML操作
變更數據捕獲存儲在當前數據庫system表中,更改表可指定存儲位置;更改跟蹤表存儲在系統架構sys中,不可查看結構定義
更多參考:比較變更數據捕獲和更改跟蹤
2 使用
下面用兩個示例簡單說明更改跟蹤和變更數據捕獲的配置及變更信息的查詢。
2.1 更改跟蹤
更改跟蹤的配置如下:
a. 在數據庫上啟用更改跟蹤(ALTER DATABASE … CHANGE_TRACKING = ON),並設置跟蹤結果保持期;
b. 在需要跟蹤更改的每個表上啟用更改跟蹤(ALTER TABLE … ENABLE CHANGE_TRACKING),並設置是否要求記錄UPDATE的列信息。(啟用更改跟蹤的表需要有主鍵)。
更改跟蹤結果的查詢包括:
a. CHANGE_TRACKING_CURRENT_VERSION
返回與上次提交的事務相關聯的版本號。啟用了更改跟蹤的數據庫具有一個版本計數器,在對啟用了更改跟蹤的表進行更改時,該計數器會隨之遞增。每個更改的行都有一個關聯的版本號。可以在每次查詢完成后,記錄這個版本號,下次查詢時,基於這個版本號查詢,以獲取后續的最新更改。
b. CHANGE_TRACKING_MIN_VALID_VERSION
指定表可用的最低有效版本號。在第一次查詢數據的時候,可以使用此函數得到查詢更改信息的起始版本號;
c. CHANGETABLE(CHANGES)
返回自指定版本起對表所做的所有更改的跟蹤信息;
d. CHANGETABLE(VERSION)
返回指定行的最新更改跟蹤信息。(通過指定特定行對應的主鍵列值);
e. CHANGE_TRACKING_IS_COLUMN_IN_MASK
通過CHANGETABLE(CHANGES …)函數返回的SYS_CHANGE_COLUMNS值及列id,確定該列是否被UPDATE。
下面的T-SQL示例創建一個測試數據庫,並在測試數據庫中演示配置更改跟蹤及查詢更改跟蹤信息。
-- ==================================================== -- 測試的數據庫 USE master; GO CREATE DATABASE DB_test; GO ALTER DATABASE DB_test SET CHANGE_TRACKING = ON( AUTO_CLEANUP = ON, -- 打開自動清理選項 CHANGE_RETENTION = 1 HOURS -- 數據保存期為1 時 ); GO -- ==================================================== -- 測試的表 USE DB_test; GO CREATE TABLE dbo.tb( id int CONSTRAINT PK_tb_id PRIMARY KEY, col1 int, col2 varchar(10), col3 nvarchar(max), col4 varbinary(max), col5 xml ); GO ALTER TABLE dbo.tb ENABLE CHANGE_TRACKING WITH( TRACK_COLUMNS_UPDATED = ON -- 記錄UPDATE 的列信息 ); GO SELECT CHANGE_TRACKING_CURRENT_VERSION(), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'dbo.tb')); GO -- ==================================================== -- 數據測試 -- a. 插入初始數據 INSERT dbo.tb( id, col1, col2, col3, col4, col5) VALUES( 1, 1, 'AA', 'AAA', 0x1, '<a>aa</a>'), ( 2, 2, 'BB', 'BBB', 0x2, '<b/>'), ( 3, 3, 'CC', 'CCC', 0x2, '<c/>'); SELECT CHANGE_TRACKING_CURRENT_VERSION(), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'dbo.tb')), * FROM CHANGETABLE(CHANGES dbo.tb, 0) CHG LEFT JOIN dbo.tb DATA ON DATA.id = CHG.id; -- b. 更新數據 BEGIN TRAN; UPDATE dbo.tb SET col1 = 11 WHERE id = 1; UPDATE dbo.tb SET col1 = 111 WHERE id = 1; COMMIT TRAN; SELECT CHANGE_TRACKING_CURRENT_VERSION(), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'dbo.tb')), * FROM CHANGETABLE(CHANGES dbo.tb, 0) CHG LEFT JOIN dbo.tb DATA ON DATA.id = CHG.id; -- c. 更新xml 和varbinary(max) 數據 UPDATE dbo.tb SET col5.modify('replace value of /a[1]/text()[1] with "replace"') WHERE id = 1; UPDATE dbo.tb SET col5.modify('insert <a>1</a> as last into /') WHERE id = 2; SELECT CHANGE_TRACKING_CURRENT_VERSION(), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'dbo.tb')), * FROM CHANGETABLE(CHANGES dbo.tb, 0) CHG LEFT JOIN dbo.tb DATA ON DATA.id = CHG.id; UPDATE dbo.tb SET col4 = col4 + 0x12345 WHERE id = 3; SELECT CHANGE_TRACKING_CURRENT_VERSION(), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'dbo.tb')), * FROM CHANGETABLE(CHANGES dbo.tb, 0) CHG LEFT JOIN dbo.tb DATA ON DATA.id = CHG.id; -- d. 更新主鍵 UPDATE dbo.tb SET id = 11 WHERE id = 1; INSERT dbo.tb( id, col1, col2, col3, col4, col5) VALUES( 1, 1, 'AA', 'AAA', 0x1, '<a>aa</a>') SELECT CHANGE_TRACKING_CURRENT_VERSION(), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'dbo.tb')), * FROM CHANGETABLE(CHANGES dbo.tb, 0) CHG LEFT JOIN dbo.tb DATA ON DATA.id = CHG.id; SELECT CHANGE_TRACKING_CURRENT_VERSION(), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(N'dbo.tb')), * FROM dbo.tb DATA OUTER APPLY CHANGETABLE(VERSION dbo.tb, (id), (DATA.id)) CHG -- ==================================================== -- 刪除測試 /*-- USE master; GO ALTER DATABASE DB_test SET SINGLE_USER WITH ROLLBACK AFTER 0; GO DROP DATABASE DB_test; --*/
2.2 變更數據捕獲
變更數據捕獲配置如下:
a. 在數據庫上啟用變更數據捕獲(調用系統存儲過程sys.sp_cdc_enable_db);
b. 通過系統存儲過程sys.sp_cdc_add_job創建捕獲和清理Job(可選,如果沒有捕獲和清理Job,會在創建數據庫中的第一個變更數據捕獲時自動建立,自動建立的Job可以通過調用系統存儲過程sys.sp_cdc_change_job來調整捕獲和清理相關的一些選項);
c. 在需要捕獲變更數據的每個表上建立變更數據捕獲實例(每個表上可以建立<=2個捕獲實例,創建捕獲實例使用系統存儲過程sys.sp_cdc_enable_table)。
捕獲的變更數據的查詢包括:
a. sys.fn_cdc_get_min_lsn
返回指定捕獲實例的有效性間隔的低端點(start_lsn);
b. sys.fn_cdc_get_max_lsn
返回cdc.lsn_time_mapping系統表的最大日志序列號(LSN);
c. cdc.fn_cdc_get_all_changes_<捕獲實例>
針對在指定日志序列號(LSN)范圍內應用到源表的每項更改均返回一行。如果源行在該間隔內有多項更改,則每項更改都會表示在返回的結果集中。除了返回更改數據外,四個元數據列還提供了將更改應用到另一個數據源所需的信息。行篩選選項可控制元數據列的內容以及結果集中返回的行。當指定“all”行篩選選項時,針對每項更改將只有一行來標識該更改。當指定“all update old”選項時,更新操作會表示為兩行:一行包含更新之前已捕獲列的值,另一行包含更新之后已捕獲列的值。
此枚舉函數是在對源表啟用變更數據捕獲時創建的。此函數名稱是派生的,采用cdc.fn_cdc_get_all_changes_capture_instance格式,其中capture_instance是在對源表啟用變更數據捕獲時為捕獲實例指定的值;
d. cdc.fn_cdc_get_net_changes_<capture_instance>
針對指定日志序列號(LSN)范圍內每個已更改的源行返回一個凈更改行。凈更改行指:如果在LSN范圍內源行具有多項更改,則該函數將返回反映該行最終內容的單一行。例如,如果事務在源表中插入一行,並且LSN范圍內的后續事務更新了該行中的一個或多個列,則該函數將只返回一行,其中包含多個更新的列值。
此枚舉函數是在對某源表啟用變更數據捕獲並指定凈跟蹤時創建的。函數名稱是派生的,采用cdc.fn_cdc_get_net_changes_capture_instance格式,其中capture_instance是對變更數據捕獲啟用源表時為捕獲實例指定的值;
e. sys.fn_cdc_map_time_to_lsn
為指定的時間返回cdc.lsn_time_mapping系統表中start_lsn列中的日志序列號(LSN)值;
f. sys.fn_cdc_has_column_changed
標識指定的更新掩碼是否指示已更新關聯的更改行中的指定列。
下面的T-SQL示例創建一個測試數據庫,並在測試數據庫中演示配置變更數據捕獲及查詢捕獲結果。
-- ==================================================== -- 測試的數據庫 USE master; GO CREATE DATABASE DB_test; GO -- 啟用變更數據捕獲 USE DB_test; EXEC sys.sp_cdc_enable_db; GO -- ==================================================== -- 檢查SQL Server Agent 服務的狀態,如果未啟動,則啟動它 DECLARE @agnt_service sysname; SET @agnt_service = N'SQLServerAgent'; DECLARE @tb_agent_status TABLE( state varchar(50) ); INSERT @tb_agent_status EXEC master.sys.xp_servicecontrol N'QUERYSTATE', @agnt_service; IF NOT EXISTS( SELECT * FROM @tb_agent_status WHERE state = N'Running.') EXEC master.sys.xp_servicecontrol N'START', @agnt_service; GO -- ==================================================== -- 測試的表 USE DB_test; GO CREATE TABLE dbo.tb( id int CONSTRAINT PK_tb_id PRIMARY KEY, col1 int, col2 varchar(10), col3 nvarchar(max), col4 varbinary(max), col5 xml ); GO -- 創建一個變更數據捕獲實例- 所有列 -- 創建數據庫中的第一個變更數據捕獲實例的時候,數據捕獲和清理的JOB 會自動創建 -- 可以通過sys.sp_cdc_change_job 這個存儲過程去調整捕獲和清理的相關設置 -- 也可以在創建第一個變更數據捕獲實例前,使用sys.sp_cdc_add_job去創建數據捕獲和清理Job,在創建時做好相關的設置 EXEC sys.sp_cdc_enable_table @source_schema = N'dbo', @source_name = N'tb', @capture_instance = N'dbo_tb', @role_name = NULL; -- 創建一個變更數據捕獲實例- 特定列 EXEC sys.sp_cdc_enable_table @source_schema = N'dbo', @source_name = N'tb', @capture_instance = N'dbo_tb_col', @role_name = NULL, @captured_column_list = N'id,col1,col2'; GO -- ==================================================== -- 數據測試 -- a. 插入初始數據 INSERT dbo.tb( id, col1, col2, col3, col4, col5) VALUES( 1, 1, 'AA', 'AAA', 0x1, '<a>aa</a>'), ( 2, 2, 'BB', 'BBB', 0x2, '<b/>'), ( 3, 3, 'CC', 'CCC', 0x2, '<c/>'); WITH LSN AS( SELECT from_lsn = sys.fn_cdc_get_min_lsn(N'dbo_tb'), to_lsn = sys.fn_cdc_get_max_lsn() ), CHG_ALL AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_all_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL UPDATE OLD') CHG ), CHG_NET AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_net_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL') CHG ) SELECT * FROM CHG_ALL; -- b. 更新數據 BEGIN TRAN; UPDATE dbo.tb SET col1 = 11 WHERE id = 1; UPDATE dbo.tb SET col1 = 111 WHERE id = 1; COMMIT TRAN; WITH LSN AS( SELECT from_lsn = sys.fn_cdc_get_min_lsn(N'dbo_tb'), to_lsn = sys.fn_cdc_get_max_lsn() ), CHG_ALL AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_all_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL UPDATE OLD') CHG ), CHG_NET AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_net_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL') CHG ) SELECT * FROM CHG_ALL; -- c. 更新xml 和varbinary(max) 數據 UPDATE dbo.tb SET col5.modify('replace value of /a[1]/text()[1] with "replace"') WHERE id = 1; UPDATE dbo.tb SET col5.modify('insert <a>1</a> as last into /') WHERE id = 2; UPDATE dbo.tb SET col4 = col4 + 0x12345 WHERE id = 3; WITH LSN AS( SELECT from_lsn = sys.fn_cdc_get_min_lsn(N'dbo_tb'), to_lsn = sys.fn_cdc_get_max_lsn() ), CHG_ALL AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_all_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL UPDATE OLD') CHG ), CHG_NET AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_net_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL') CHG ) SELECT * FROM CHG_ALL; -- d. 更新主鍵 UPDATE dbo.tb SET id = 11 WHERE id = 1; INSERT dbo.tb( id, col1, col2, col3, col4, col5) VALUES( 1, 1, 'AA', 'AAA', 0x1, '<a>aa</a>'); WITH LSN AS( SELECT from_lsn = sys.fn_cdc_get_min_lsn(N'dbo_tb'), to_lsn = sys.fn_cdc_get_max_lsn() ), CHG_ALL AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_all_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL UPDATE OLD') CHG ), CHG_NET AS( SELECT CHG.* FROM LSN CROSS APPLY cdc.fn_cdc_get_net_changes_dbo_tb(LSN.from_lsn, LSN.to_lsn, 'ALL') CHG ) SELECT * FROM CHG_ALL; -- ==================================================== -- 刪除測試 /*-- USE master; GO ALTER DATABASE DB_test SET SINGLE_USER WITH ROLLBACK AFTER 0; GO DROP DATABASE DB_test; --*/