初探 SQL Server 2008 中的更改跟蹤與變更數據捕獲

1                概述

1.1        更改跟蹤

1.2        變更數據捕獲

1.3        比較更改跟蹤和變更數據捕獲

2                使用

2.1        更改跟蹤

2.2        變更數據捕獲 


1         概述

“更改跟蹤”和“變更數據捕獲”捕獲和記錄用戶表的DML更改(插入、更新和刪除操作),爲某些有特殊需求的應用程序服務。

1.1   更改跟蹤

更改跟蹤捕獲表的數據行更改這一行爲,但不會捕獲更改的具體數據。捕獲的結果包含表的主鍵及相關的跟蹤信息(例如更改的操作類型、更新操作影響的列等)。

應用程序可以利用這個捕獲的結果來確定表的最新更新,並可以關聯原始來來獲取最新的數據。

1.2   變更數據捕獲

變更數據捕獲使用異步進程讀取事務日誌,獲取DML更改實際數據做爲數據捕獲的結果。在捕獲結果中,還包含更改相關的一些信息(例如更改的操作類型、更新操作影響的列等)。

應用程序可以從捕獲結果中獲取DML更改的全部數據,而無需查詢數據變更的原始表。

1.3   比較更改跟蹤和變更數據捕獲

比較更改跟蹤和變更數據捕獲,它們的異同情況如下表所示。

功能

更改跟蹤

變更數據捕獲

跟蹤的更改

DML更改(插入、刪除、更新)

DML更改(插入、刪除、更新)

跟蹤的信息

更新涉及的列

DML類型

更改行的主鍵列值

更新涉及的列

DML類型

歷史變更數據

適用的版本

所有版本

僅企業版

實時性

與數據更改同步

異步讀取事務日誌

可控性

n  數據庫級別的自動清理設置

啓用或禁用,及跟蹤信息的保存期

n  表級別的跟蹤設置

啓用或者禁用,可選擇是否記錄UPDATE操作影響的列

無法限制要跟蹤的更改類型

n  數據庫級別的清理及捕獲設置

啓用或禁用捕獲/清除作業,及作業相關的參數:例如數據捕獲的模式(單次觸發模式和連續模式)、清除作業要保留的捕獲記錄的保留期等

n  表級別的捕獲設置

啓用或者禁用,每個表可以設置<=2個捕獲實例,可選擇是否要捕獲的列的列表及爲啓用淨更改記錄查詢提供的唯一索引名

無法限制要捕獲的數據變更類型

歷史數據查詢

無法查詢歷史數據

對於變更信息的查詢,僅能查詢最新更改跟蹤信息(起始於某個版本,或者基於某個特定的主鍵值的最新跟蹤信息)

只要歷史數據未被清除,就可以查詢

對錶的要求

表必須有主鍵

無特別要求

對錶結構修改的限制

無法進行與主鍵相關的DDL操作(刪除主鍵定義、修改主鍵列定義、禁用主鍵)

爲表啓用變更數據捕獲後,只能由服務器角色sysadmin成員、數據庫角色db_ownerdb_ddladmin 成員能將DDL操作應用於該表;

另外,修改@index_name參數指定的唯一索引或者相關列之前,需要先禁用數據變更捕獲才能修改。

@index_name在啓用表的變更數據捕獲設定,如果未指定,但該表有主鍵,則@index_name爲該表的主鍵名稱(主鍵在啓用變更數據捕獲後建立的話,不受此限制)

特別注意:
修改表結構之前,還應該檢查數據捕獲表中的數據,如果表結構修改操作無法應用到數據捕獲表中,則會導致數據捕獲Job失敗。例如,如果要把某個列的定義從varchar改成int,原始表中該列的數據都可以轉換成int,但數據捕獲表中包含無法轉換成int的數據,則表結構修改操作會成功,但由於數據捕獲表無法應用這個DDL,所以數據捕獲的Job會失敗

TRUNCATE TABLE

不會跟蹤TRUNCATE TABLE刪除的行,並且會更新最低有效版本。當應用程序檢查其版本時,檢查結果會表明該版本太陳舊,需要進行重新初始化。這與禁用後又重新啓用表的更改跟蹤的效果相同

不允許

ALTER TABLE SWITCH

不允許

在配置時可以設置是否允許,但始終不會捕獲此操作導致的數據變更

SQL Agent的要求

使用Job進行數據捕獲及歷史數據清除,因此需要SQL Agent服務的支持

DML的影響

與添加一個索引導致的性能開銷差不多

異步讀取事務日誌來獲取數據,所以基本上不會有影響。

日誌讀取使用sp_replcmds存儲過程,如果數據庫上配置有事務複製,則與事務複製共用日誌讀取器,否則使用單獨的job

對存儲的影響

n  內部更改表

啓用了更改跟蹤的每個用戶表都有一個內部更改表。對於用戶表中每行的每個更改,都會向內部更改表中添加一行,該行的大小=較小的固定開銷+大小等於主鍵列大小的可變開銷+由應用程序設置的可選上下文信息(使用WITH CHANGE_TRACKING_CONTEXT+UPDATE影響的列(4字節,啓用列跟蹤的情況下才有)

n  內部事務表

每個數據庫一個。對於每個已提交的事務,都會向內部事務表中添加一行

每個捕獲的實例對應一張名爲cdc.<capture_instance>_CT的變更表,該表包含捕獲實例需要捕獲的所有列及捕獲信息列。對源表執行的每個插入和刪除操作,都會在變更表中插入一行;爲對源表執行的每個更新操作,將插入兩行:一行爲更新前的值,一行爲更新後的值。

其他

 

Ø  對於稀疏列,不支持在使用列集時捕獲更改

Ø  不跟蹤xml類型列中,對單個 XML元素的更改


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;

--*/

 

發佈了181 篇原創文章 · 獲贊 45 · 訪問量 143萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章