前段时间评估了一下SQL Server 2008的CDC功能,总体发现CDC的开销还是不小的,特别是对日志文件的读。对于那些字段数量较多而且需要跟踪每个字段值更新的用户表,CDC的带来的额外开销则尤其明显。因此继续评估SQL Server 2008对数据更新跟踪的技术方案。
在SQL Server 2008的联机丛书中提到了,Change Tracking适用于单向或双向的数据同步应用场景。在接下来的一些列试用中,我们就能深刻体会到这一点。
要使用Change Tracking,首先需要在数据库级别启用Change Tracking功能,我们可以通过两种途径启用Change Tracking。
其中我们会注意到CHANGE_RETENTION参数和AUTO_CLEANUP参数。因为Change Tracking所有的信息都会被添加到一张内部表中,所以这张内部表将会无限制地增长,如果我们不希望这张表的数据量一直增长,Change Tracking功能提供了一个后台的进程自动对内部表进行清理,清理的依据就是CHANGE_RETENTION,每次清理进程运行的时候都会将超过CHANGE_RETENTION参数中设定时间的记录删除掉,而清理进程是否会运行则依赖于AUTO_CLEANUP参数。CHANGE_RETENTION参数的时间单位可以是分钟、小时或天。
激活数据库级别的Change Tracking功能后,DBA就可以选择哪些需要更新跟踪的表了。我们同样也有两种方法在更新跟踪的表上启用Change Tracking功能:
其中我们注意到有个参数是TRACK_COLUMN_UPDATED,当这个参数被设置为ON的时候,UPDATE语句提交后在内部表中将会记录UPDATE语句影响了哪些列,对于INSERT和DELETE语句,则所有列都是被影响到的。
Change Tracking提供的信息可不像Change Data Capture那么详细,Change Data Capture可以提供每个事务影响到的数据的前像和后像。Change Tracking功能通过CHANGETABLE系统表来获得更新的版本信息。
CHANGETABLE有两种用法,一种是CHANGETABLE(CHANGES),一种是CHANGETABLE(VERSION)。前者用于返回某个sync_version后的数据变化情况,后者用于返回某行数据的最新的更改版本号。
这是一个表函数,语法结构为CHANGETABLE(CHANGES table, last_sync_version),table参数为激活Change Tracking功能的表名,last_sync_version参数是希望获取更新的最小版本号。
table参数的值非常明白,我们希望获得哪张表的更新信息,就是这张表的名字了,而last_sync_version是影响返回结果集的主要因素。
举个例子,我们用下面这段脚本来详细说明Change Tracking记录下的信息:
CREATE TABLE TestCT
(
ID int PRIMARY KEY,
Name varchar(50),
Description varchar(200)
)
GO
ALTER TABLE TestCT
ENABLE CHANGE_TRACKING
WITH (TRACK_COLUMNS_UPDATED = ON)
GO
这个时候TestCT表的Change Tracking功能就被激活了,这张表上所有的数据更新都会被记录到一张内部表中,接着我们就可以在TestCT表上提交各种DML语句了。
INSERT INTO TestCT VALUES (1, 'ABC', NULL)
这个时候我们可以通过CHANGETABLE函数来查看TestCT的更新历史
SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT
返回结果为
| SYS_CHANGE _VERSION | SYS_CHANGE _CREATION_VERSION | SYS_CHANGE _OPERATION | SYS_CHANGE _COLUMNS | SYS_CHANGE _CONTEXT | ID |
| 1 | 1 | I | NULL | NULL | 1 |
返回结果集中:
我们接着测试,
UPDATE TestCT SET Name = 'abc' WHERE ID = 1
提交完这句语句后,其实TestCT表已经有了两次更新,一次是INSERT操作,一次是UPDATE操作,但是这两次操作都是针对ID为1的这一行数据,这个时候我们在CHANGETABLE函数中通过不同的last_sync_version参数会得到不同的返回结果。
SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT
返回结果为
| SYS_CHANGE _VERSION | SYS_CHANGE _CREATION_VERSION | SYS_CHANGE _OPERATION | SYS_CHANGE _COLUMNS | SYS_CHANGE _CONTEXT | ID |
| 2 | 1 | I | NULL | NULL | 1 |
从返回结果看,主键值为1的这行数据最近的更新版本为2,而插入表时的更新版本为1,代表这行数据在插入数据表后已经有过更新,但是SYS_CHANGE_OPERATION仍然是I。原因是我们选取的last_sync_version参数值是0,代表我们上次同步时数据更新版本是0,而这行数据是在版本0后被插入表的,因此这行数据还没有出现在同步的目的表中,因此在我们进行同步时首先要处理INSERT操作,而被INSERT到源表中的原始记录已经被更新了(在版本2时),因此我们只能将INSERT操作和UPDATE操作进行合并,直接取得UPDATE后的数据行写入目的表。
但是如果我们提交的语句是
SELECT * FROM CHANGETABLE(CHANGES TestCT, 1) CT
返回结果则会变为
| SYS_CHANGE _VERSION | SYS_CHANGE _CREATION_VERSION | SYS_CHANGE _OPERATION | SYS_CHANGE _COLUMNS | SYS_CHANGE _CONTEXT | ID |
| 2 | 1 | U | 0x0000000002000000 | NULL | 1 |
这次SYS_CHANGE_OPERATION字段变成了U。原因是我们选取的last_sync_version参数值是1,代表我们上次同步时数据更新版本是1,也就意味着这行数据已经被同步到目的表中了,因此在我们进行同步时只需要处理UPDATE操作。
接着我们尝试一下
UPDATE TestCT SET ID = 5 WHERE ID = 1
然后我们看看CHANGETABLE会返回什么
SELECT * FROM CHANGETABLE(CHANGES TestCT, 2) CT
返回结果则会变为
| SYS_CHANGE _VERSION | SYS_CHANGE _CREATION_VERSION | SYS_CHANGE _OPERATION | SYS_CHANGE _COLUMNS | SYS_CHANGE _CONTEXT | ID |
| 3 | NULL | D | NULL | NULL | 1 |
| 3 | 3 | I | NULL | NULL | 5 |
返回结果会变成两条,这是因为我们这次UPDATE修改的是主键,所以在表中其实是将原始记录删除,然后增加一行新的数据。在同步的时候我们就知道需要删除先前同步到目的表中的ID为1的记录,然后复制源表中ID为5的记录到目的表中。
接下来还可以进行一系列的测试,比如说:
这个表函数的语法结构为CHANGETABLE(VERSION table , { ( column_name [ , ...n ] ) , ( value [ , ...n ] ) },table参数是激活Change Tracking功能的表名,column_name是主键的字段名,如果主键是复合键,则需要列举主键中所有的字段,value是对应前面每个字段的值。例如上面代码,我们可以通过下面语句来得到ID为5的记录最近的更新版本号是多少:
SELECT * FROM CHANGETABLE(VERSION TestCT, (ID), (5)) CT
返回结果则会变为
|
SYS_CHANGE |
SYS_CHANGE |
ID |
| 3 | NULL | 5 |
由返回结果可以了解到ID为5的数据在当前系统中最后一次更新的更新版本号是3,如果在另外一个系统中ID为5的数据版本号不是3的话,那么就意味着需要进行复制了。
CHANGETABLE(VERSION)函数与CHANGETABLE(CHANGES)函数最大的区别就在于它可以传入表的主键值,因此可以根据用户的需求了解每一行数据的最新版本号,而CHANGETABLE(CHANGES)则是通过某个版本号来获得自从这个版本号之后的更新信息。因此两个函数可以用于两种不同的复制拓扑,前者更加适用于双向复制,而后者则适用于单向复制。
下面给出完整的测试脚本:
/*************************************************************
**************************************************************
Section 1: How to enable the change tracking
**************************************************************
**************************************************************/
--Create test database and enable the change tracking feature of the database
USE master
GO
CREATE DATABASE TestCT
GO
ALTER DATABASE TestCT
SET Change_Tracking = ON
(CHANGE_RETENTION = 2 MINUTES, AUTO_CLEANUP = ON)
GO
--Create test table and enable the change tracking feature on the table
USE TestCT
GO
CREATE TABLE TestCT
(
ID int PRIMARY KEY,
Name varchar(50),
Description varchar(200)
)
GO
ALTER TABLE TestCT
ENABLE CHANGE_TRACKING
WITH (TRACK_COLUMNS_UPDATED = ON)
GO
--Use sys.change_tracking_tables to get the table list which enabled change tracking
SELECT OBJECT_NAME(object_id) AS table_name, sys.change_tracking_tables.* FROM sys.change_tracking_tables
GO
/*************************************************************
**************************************************************
Section 2: How to get change tracking information
**************************************************************
**************************************************************/
--The current version is 0 now.
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
--Insert two rows in two transactions
INSERT INTO TestCT VALUES (1, 'ABC', NULL)
INSERT INTO TestCT VALUES (2, 'XYZ', NULL)
--The current version is 2 now.
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
--Get the changed table version with table function 'CHANGETABLE'.
--You will see two rows with different SYS_CHANGE_VERSION and SYS_CHANGE_CREATION_VERSION.
SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT
--INSERT two rows in one transaction
BEGIN TRAN
INSERT INTO TestCT VALUES (3, 'CBA', NULL)
INSERT INTO TestCT VALUES (4, 'ZYX', NULL)
COMMIT
--The current version is 3 now.
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
--Get the changed table version with table function 'CHANGETABLE'.
--You will see two rows with same SYS_CHANGE_VERSION and SYS_CHANGE_CREATION_VERSION.
--We use 2 as the last_sync_version value here to see net changes of after the last time insertion.
SELECT * FROM CHANGETABLE(CHANGES TestCT, 2) CT
--DML update which not affected the primary key
UPDATE TestCT SET Name = 'abc' WHERE ID = 1
--The current version is 4 now.
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
--Get the changed table version with table function 'CHANGETABLE'.
--With different last_sync_version, you will see the different result
SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT --the SYS_CHANGE_OPERATION is I now
SELECT * FROM CHANGETABLE(CHANGES TestCT, 3) CT --the SYS_CHANGE_OPERATION is U now
--With the CHANGE_TRACKING_IS_COLUMN_IN_MASK function, you will see which row changed.
SELECT CHANGE_TRACKING_IS_COLUMN_IN_MASK
(COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Name', 'ColumnId'), SYS_CHANGE_COLUMNS)
AS is_column_Name_changed,
CHANGE_TRACKING_IS_COLUMN_IN_MASK
(COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Description', 'ColumnId'), SYS_CHANGE_COLUMNS)
AS is_column_Description_changed
FROM CHANGETABLE(CHANGES TestCT, 3) CT
--DML update which affected the primary key
UPDATE TestCT SET ID = 5 WHERE ID = 1
--The current version is 5 now.
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
--Get the changed table version with table function 'CHANGETABLE'
--you will see one record indicate the deletion of origional record
--and one record indicate the insertion of new record
SELECT * FROM CHANGETABLE(CHANGES TestCT, 4) CT
--DML delete
DELETE TestCT WHERE ID = 5
--The current version is 6 now.
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
--Get the changed table version with table function 'CHANGETABLE'
--you will see one record indicate the deletion of origional record
SELECT * FROM CHANGETABLE(CHANGES TestCT, 5) CT
--Another usefull usage of CHANGETABLE, which can help you
--to determine the current version of a specific row
SELECT * FROM CHANGETABLE(VERSION TestCT, (ID), (4)) CT
--Another usefull statement which can help you distinguish the
--change context
DECLARE @context AS varbinary(128);
SET @context = CAST('Test_Change_Tracking' AS varbinary(128));
WITH CHANGE_TRACKING_CONTEXT (@context)
UPDATE TestCT SET Description = 'NA';
-- The change now has an associated change context
SELECT CT.SYS_CHANGE_VERSION, CT.SYS_CHANGE_OPERATION,
CAST(SYS_CHANGE_CONTEXT AS varchar(20)) AS change_context
FROM CHANGETABLE(CHANGES TestCT, 6) AS CT
--Another usefull function which can help you determine the requirement
--of re-initialization.
--Before the truncation or periodical cleanup, the min_valid_version is 0 now.
SELECT CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID('TestCT')) AS min_valid_version;
--After the truncation or periodical cleanup, the min_valid_version is 7 now.
TRUNCATE TABLE TestCT;
SELECT CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID('TestCT')) AS min_valid_version;
--So the insert statement will use 8 as the SYS_CHANGE_VERSION
INSERT INTO TestCT VALUES (1, 'ABC', NULL)
--So the last_sync_version parameter will not affect the result now
SELECT * FROM CHANGETABLE(CHANGES TestCT, 0) CT
SELECT * FROM CHANGETABLE(CHANGES TestCT, 7) CT
/*************************************************************
**************************************************************
Section 3: DDL affection to the change tracking
**************************************************************
**************************************************************/
--After the tables schema changed, the new column will also be monitored
ALTER TABLE TestCT ADD Age int;
UPDATE TestCT SET Age = 10;
SELECT CHANGE_TRACKING_IS_COLUMN_IN_MASK
(COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Name', 'ColumnId'), SYS_CHANGE_COLUMNS)
AS is_column_Name_changed,
CHANGE_TRACKING_IS_COLUMN_IN_MASK
(COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Description', 'ColumnId'), SYS_CHANGE_COLUMNS)
AS is_column_Description_changed,
CHANGE_TRACKING_IS_COLUMN_IN_MASK
(COLUMNPROPERTY(OBJECT_ID('TestCT'), 'Age', 'ColumnId'), SYS_CHANGE_COLUMNS)
AS is_column_Age_changed
FROM CHANGETABLE(CHANGES TestCT, 8) CT;
--You will find the schema modification will not be tracked.
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
ALTER TABLE TestCT DROP COLUMN Age
SELECT CHANGE_TRACKING_CURRENT_VERSION() AS current_version
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/9079672/viewspace-220318/ ,如需转载,请注明出处,否则将追究法律责任。