使用更改跟踪实现数据同步

 

SQL Server 2008 引入了更改跟踪,这是一种轻量型解决方案,它为应用程序提供了一种有效的更改跟踪机制。通常,若要使应用程序能够查询对数据库中的数据所做的更改和访问与这些更改相关的信息,应用程序开发人员必须实现自定义更改跟踪机制。创建这些机制通常涉及多项工作,并且常常涉及使用触发器、timestamp 列和新表组合来存储跟踪信息,同时还会涉及使用自定义清除过程。

通过更改跟踪,可以很容易地编写同步数据的应用,下面是一个使用更改跟踪实现单向数据同步的示例。

1 建立示例环境

-- ====================================================

-- 测试的数据库

USE master;

GO

CREATE DATABASE DB_test;

GO

-- 启用更改跟踪

ALTER DATABASE DB_test SET

    CHANGE_TRACKING = ON(

           AUTO_CLEANUP = ON,          -- 打开自动清理选项

           CHANGE_RETENTION = 1 HOURS  -- 数据保存期为1

    );

 

ALTER DATABASE DB_test SET

    ALLOW_SNAPSHOT_ISOLATION ON;       -- 允许在测试数据库中使用 SNAPSHOT 事务隔离级别

GO

 

-- ====================================================

-- 测试的表

USE DB_test;

GO

-- a. 同步的源表

CREATE TABLE dbo.tb_source(

    pk_id int IDENTITY

       PRIMARY KEY,

    col1 int,

    col2 varchar(10),

    col3 nvarchar(max),

    col4 xml

);

GO

-- 启用更改跟踪

ALTER TABLE dbo.tb_source

    ENABLE CHANGE_TRACKING

       WITH(

           TRACK_COLUMNS_UPDATED = ON  -- 记录UPDATE 的列信息

       );

GO

 

-- b. 同步的目录表

CREATE TABLE dbo.tb_Target(

    pk_id int

       PRIMARY KEY,

    col1 int,

    col2 varchar(10),

    col3 nvarchar(max),

    col4 xml

);

GO

 

-- 记录同步情况的表

CREATE TABLE dbo.tb_Change_Tracking(

    id int IDENTITY

       PRIMARY KEY,

    object_name sysname

       UNIQUE,

    last_sync_version bigint,

    last_update_date datetime

);

GO

 

2 实现同步处理的存储过程

-- ====================================================

-- 数据同步处理的存储过程

USE DB_test;

GO

-- 数据同步的存储过程- 同步未更新的数据

-- 单次更新,更新完成后退出

CREATE PROC dbo.p_SyncChangeData_tb_Srouce_Target

    @last_sync_version bigint = NULL OUTPUT,

    @min_valid_version bigint = NULL OUTPUT

AS

SET NOCOUNT ON;

-- ========================================

-- TRY...CATCH 中的标准事务处理模块

-- a. 当前的事务数

DECLARE

    @__trancount int;

SELECT

    @__trancount = @@TRANCOUNT;

 

-- TRY...CATCH 处理

BEGIN TRY

    -- ========================================

    -- 源表信息

    DECLARE

       @object_name sysname,

       @object_id int;

    SELECT

       @object_name = N'dbo.tb_source',

        @object_id = OBJECT_ID(@object_name);

 

    -- ========================================

    -- 最后一次同步的版本

    IF @last_sync_version IS NULL

    BEGIN

       SELECT

           @last_sync_version = last_sync_version

       FROM dbo.tb_Change_Tracking

       WHERE object_name = @object_name;

 

       IF @@ROWCOUNT = 0

       BEGIN

           SET @last_sync_version = CHANGE_TRACKING_MIN_VALID_VERSION(@object_id);

           INSERT dbo.tb_Change_Tracking(

              object_name, last_sync_version)

           VALUES(

              @object_name, @last_sync_version);

       END;

    END;

 

    -- ========================================

    -- TRY...CATCH 中的标准事务处理模块

    -- b. 开启事务, 或者设置事务保存点

    SET TRANSACTION ISOLATION LEVEL SNAPSHOT; -- 使用快照隔离级别的事务

    IF @__trancount = 0

       BEGIN TRAN;

    ELSE

       SAVE TRAN __TRAN_SavePoint;

 

    -- ========================================

    -- 版本验证

    -- a. 验证是否有数据变更(如果上次同步的版本号= 当前数据库的最大版本号,则视为无数据变化)

    IF @last_sync_version = CHANGE_TRACKING_CURRENT_VERSION()

       GOTO lb_Return;

 

    -- b. 验证同步的版本号是否有效(如果上次同步的版本号< 当前可用的最小版本号,则视为无效)

    IF @last_sync_version < CHANGE_TRACKING_MIN_VALID_VERSION(@object_id)

    BEGIN

       SET @min_valid_version = CHANGE_TRACKING_MIN_VALID_VERSION(@object_id);     

       GOTO lb_Return;

    END;

 

    -- c. 验证同步的版本号是否有效(如果上次同步的版本号> 当前数据库的最大版本号,则视为无效)

    IF @last_sync_version > CHANGE_TRACKING_CURRENT_VERSION()

    BEGIN

       SET @last_sync_version = NULL;

       GOTO lb_Return;

    END;

 

    -- ========================================

    -- 同步数据

    -- a. 插入

    WITH

    CHG AS(

       SELECT

           DATA.*

       FROM dbo.tb_source DATA

           INNER JOIN CHANGETABLE(CHANGES dbo.tb_source, @last_sync_version) CHG

              ON CHG.pk_id = DATA.pk_id

       WHERE CHG.SYS_CHANGE_OPERATION = N'I'

    )

    INSERT dbo.tb_Target

    SELECT * FROM CHG;

 

    -- b. 删除

    WITH

    CHG AS(

       SELECT

           CHG.*

       FROM CHANGETABLE(CHANGES dbo.tb_source, @last_sync_version) CHG

       WHERE CHG.SYS_CHANGE_OPERATION = N'D'

    )

    DELETE DATA

    FROM dbo.tb_Target DATA

       INNER JOIN CHG

           ON CHG.pk_id = DATA.pk_id;

 

    -- c. 更新

    WITH

    COL AS(

       SELECT

           *

       FROM(

           SELECT

              name, column_id

           FROM sys.columns

           WHERE object_id = @object_id

       )DATA

           PIVOT(

              MAX(column_id)

              FOR name IN(  -- 源表的所有列的列表

                  [col1], [col2], [col3], [col4])

           )P               

    ),

    CHG AS(

       SELECT

           DATA.*,

           __SYS_CHANGE_COLUMNS = CHG.SYS_CHANGE_COLUMNS

       FROM dbo.tb_source DATA

           INNER JOIN CHANGETABLE(CHANGES dbo.tb_source, @last_sync_version) CHG

              ON CHG.pk_id = DATA.pk_id

       WHERE CHG.SYS_CHANGE_OPERATION = N'U'

    )

    UPDATE DATA SET             -- 判断列是否需要更新(也可以不判断,直接更新所有的列)

       [col1] = CASE

                  WHEN CHG.__SYS_CHANGE_COLUMNS IS NULL OR CHANGE_TRACKING_IS_COLUMN_IN_MASK(COL.[col1], CHG.__SYS_CHANGE_COLUMNS) = 1

                     THEN CHG.[col1]

                  ELSE DATA.[col1]

              END,

       [col2] = CASE

                  WHEN CHG.__SYS_CHANGE_COLUMNS IS NULL OR CHANGE_TRACKING_IS_COLUMN_IN_MASK(COL.[col2], CHG.__SYS_CHANGE_COLUMNS) = 1

                     THEN CHG.[col2]

                  ELSE DATA.[col2]

              END,

       [col3] = CASE

                  WHEN CHG.__SYS_CHANGE_COLUMNS IS NULL OR CHANGE_TRACKING_IS_COLUMN_IN_MASK(COL.[col3], CHG.__SYS_CHANGE_COLUMNS) = 1

                     THEN CHG.[col3]

                  ELSE DATA.[col3]

              END,

       [col4] = CASE

                  WHEN CHG.__SYS_CHANGE_COLUMNS IS NULL OR CHANGE_TRACKING_IS_COLUMN_IN_MASK(COL.[col4], CHG.__SYS_CHANGE_COLUMNS) = 1

                     THEN CHG.[col4]

                  ELSE DATA.[col4]

              END

    FROM dbo.tb_Target DATA

       INNER JOIN CHG

           ON CHG.pk_id = DATA.pk_id

       CROSS JOIN COL;

   

    -- ========================================

    -- 更新最后一次同步的版本号

    UPDATE dbo.tb_Change_Tracking SET

       @last_sync_version = CHANGE_TRACKING_CURRENT_VERSION(),

 

       last_sync_version = @last_sync_version,

       last_update_date = GETDATE();

 

    -- ========================================

    -- TRY...CATCH 中的标准事务处理模块

    -- c. 提交事务

    --    有可提交的事务, 并且事务是在当前模块中开启的情况下, 才提交事务

    IF XACT_STATE() = 1 AND @__trancount = 0

       COMMIT;

 

lb_Return:

    -- ========================================

    -- TRY...CATCH 中的标准事务处理模块

    -- d. 为了防止TRY 块中有遗漏的事务处理, TRY 模块的结束部分做最终的事务处理

    IF @__trancount = 0

    BEGIN

       IF XACT_STATE() = -1

           ROLLBACK TRAN;

       ELSE

       BEGIN     

           WHILE @@TRANCOUNT > 0

              COMMIT TRAN;

       END;

    END;

END TRY

BEGIN CATCH

    -- ========================================

    -- TRY...CATCH 中的标准事务处理模块

    -- e. CATCH 块的事务回滚

    IF XACT_STATE() <> 0

    BEGIN

       IF @__trancount = 0

           ROLLBACK TRAN;

       -- XACT_STATE -1 , 不能回滚到事务保存点, 这种情况留给外层调用者做统一的事务回滚

       -- 通过@@TRANCOUNT > @__trancount 的判断, 即使在TRY 模块中没有设置事务保存点的情况下跳到此步骤, 也不会出错

       ELSE IF XACT_STATE() = 1 AND @@TRANCOUNT > @__trancount

           ROLLBACK TRAN __TRAN_SavePoint;

    END;

 

    -- ========================================

    -- 错误消息处理

    -- a. 获取错误信息

    --    这提提取了错误相关的全部信息, 可以根据实际需要调整

    DECLARE

       @__error_number int,

       @__error_message nvarchar(2048),

       @__error_severity int,

       @__error_state int,

       @__error_line int,

       @__error_procedure nvarchar(126),

       @__user_name nvarchar(128),

       @__host_name nvarchar(128);

 

    SELECT

       @__error_number = ERROR_NUMBER(),

       @__error_message = ERROR_MESSAGE(),

       @__error_severity = ERROR_SEVERITY(),

       @__error_state = ERROR_STATE(),

       @__error_line = ERROR_LINE(),

       @__error_procedure = ERROR_PROCEDURE(),

       @__user_name = SUSER_SNAME(),

       @__host_name = HOST_NAME();

 

    -- c. 如果没有打算在CATCH 模块中对错误进行处理, 则应该抛出错误给调用者

    RAISERROR(

       N'User: %s, Host: %s, Procedure: %s, Error %d, Level %d, State %d, Line %d, Message: %s ',

       @__error_severity,

       1,

       @__user_name,

       @__host_name,

       @__error_procedure,

       @__error_number,

       @__error_severity,

       @__error_state,

       @__error_line,

       @__error_message);

END CATCH;

GO

 

-- 数据同步的存储过程- 同步未更新的数据

-- 循环更新,直到手工结束

-- 可设置一个在SQL Agent服务启动时工作的JOB 来调用这个存储过程,这样就可以实现自动同步

CREATE PROC dbo.p_SyncChangeData_Circle_tb_Srouce_Target

    @interval int = 1 -- 循环检测之间相隔的秒数(指定负数或NULL值会调整为)

AS

SET NOCOUNT ON;

 

IF @interval IS NULL OR @interval < 0

    SET @interval = 1;

 

DECLARE

    @last_sync_version bigint,

    @min_valid_version bigint,

    @date_delay datetime;

 

WHILE 1 = 1

BEGIN

    EXEC dbo.p_SyncChangeData_tb_Srouce_Target

       @last_sync_version = @last_sync_version OUTPUT,

       @min_valid_version = @min_valid_version OUTPUT;

 

    IF @min_valid_version IS NOT NULL

    BEGIN

       RAISERROR(N'某些未同步的数据已经丢失', 16, 1);

       BREAK;

    END;

    IF @last_sync_version IS NULL

    BEGIN

       RAISERROR(N'源数据异常,同步版本号小于已经同步的版本号', 16, 1);

       BREAK;

    END;

   

    SET @date_delay = DATEADD(Second, @interval, GETDATE());

    WAITFOR TIME @date_delay;

END

GO

 

3.同步测试

-- ====================================================

-- 测试

USE DB_test;

GO

-- A.01. 变更源表数据

INSERT dbo.tb_source(

    col1)

VALUES(

    1);

 

INSERT dbo.tb_source(

    col1)

VALUES(

    2),

(

    3);

 

UPDATE dbo.tb_source SET

    col2 = 'update'

WHERE pk_id = 2;

 

-- A.02. 同步源表数据,并查询同步结果

DECLARE

    @last_sync_version bigint,

    @min_valid_version bigint;

 

EXEC dbo.p_SyncChangeData_tb_Srouce_Target

    @last_sync_version = @last_sync_version OUTPUT,

    @min_valid_version = @min_valid_version OUTPUT;

IF @min_valid_version IS NOT NULL

    RAISERROR(N'某些未同步的数据已经丢失', 16, 1);

IF @last_sync_version IS NULL

    RAISERROR(N'源数据异常,同步版本号小于已经同步的版本号', 16, 1);

 

SELECT * FROM dbo.tb_source;

SELECT * FROM dbo.tb_Target;

SELECT * FROM dbo.tb_Change_Tracking;

 

-- B.01. 变更源表数据

INSERT dbo.tb_source(

    col1)

VALUES(

    11);

 

UPDATE dbo.tb_source SET

    col2 = 'update.1'

WHERE pk_id = 2;

 

UPDATE dbo.tb_source SET

    col2 = 'update 3'

WHERE pk_id = 3;

 

DELETE FROM dbo.tb_source

WHERE pk_id < 3;

 

SET IDENTITY_INSERT dbo.tb_source ON;

INSERT dbo.tb_source(

    pk_id, col1, col2)

VALUES(

    1, 11, 'insert');

SET IDENTITY_INSERT dbo.tb_source OFF;

 

-- B.02. 同步源表数据,并查询同步结果

EXEC dbo.p_SyncChangeData_tb_Srouce_Target

    @last_sync_version = @last_sync_version OUTPUT,

    @min_valid_version = @min_valid_version OUTPUT;

IF @min_valid_version IS NOT NULL

    RAISERROR(N'某些未同步的数据已经丢失', 16, 1);

IF @last_sync_version IS NULL

    RAISERROR(N'源数据异常,同步版本号小于已经同步的版本号', 16, 1);

 

SELECT * FROM dbo.tb_source;

SELECT * FROM dbo.tb_Target;

SELECT * FROM dbo.tb_Change_Tracking;

GO

 

4.删除示例测试环境

-- ====================================================

-- /* -- 删除测试环境

USE DB_test;

GO

IF OBJECT_ID(N'dbo.p_SyncChangeData_tb_Srouce_Target') IS NOT NULL

    DROP PROC dbo.p_SyncChangeData_tb_Srouce_Target;

 

IF OBJECT_ID(N'dbo.tb_source') IS NOT NULL

    DROP TABLE dbo.tb_source;

 

IF OBJECT_ID(N'dbo.tb_Target') IS NOT NULL

    DROP TABLE dbo.tb_Target;

 

IF OBJECT_ID(N'dbo.tb_Change_Tracking') IS NOT NULL

    DROP TABLE dbo.tb_Change_Tracking;

GO

--*/

/*-- 删除测试数据库

USE master;

GO

ALTER DATABASE DB_test SET

    SINGLE_USER

    WITH

       ROLLBACK AFTER 0;

GO

DROP DATABASE DB_test;

--*/

 

附:

关于使用更改跟踪实现数据同步的一些补充说明:

1.  使用 SNAPSHOT 事务隔离级别

在进行同步的处理中,需要记录当前已经处理的最后一个版本号;而在查询更改跟踪信息时,无法指定要查询的更改跟踪信息的结束版本号。这就要求同步处理过程中,无论是查询更改的最后一个版本号,还是查询更改跟踪信息,都能保证是同一个变更版本的结束版本号。把所有的处理放在事务中,并使用SNAPSHOT事务隔离级别可以保证这点。

2.  自动同步

更改跟踪的信息只能被查询,所以在示例中,是在数据变更后,手动运行同步的存储过程实现数据同步。如果要自动同步,则只能使用定时查询的方式。本示例提供了一个名为“dbo.p_SyncChangeData_Circle_tb_Srouce_Target”的存储过程,可以使用Job或者其他程序在SQL Server启动时,调用这个存储过程,则这个存储过程会定时检查数据变更,并实现数据变更的自动同步。

 

 

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值