更改跟踪如何处理对数据库的更改

某些使用更改跟踪的应用程序执行与另一个数据存储区的双向同步。 即,在一个 SQL Server 数据库中所做的更改将更新到另一个数据存储区中,而在该数据存储区中所做的更改将更新到该 SQL Server 数据库中。

当应用程序使用另一个数据存储区中的更改更新本地数据库时,应用程序必须执行以下操作:

  • 检查冲突。

    如果在两个数据存储区中同时更改相同的数据,则会发生冲突。 应用程序必须能够检查冲突,并获取足够的信息以便能够解决冲突。

  • 存储应用程序上下文信息。

    应用程序存储具有更改跟踪信息的数据。 如果更改是从本地数据库中获取的,则会将此信息与其他更改跟踪信息放在一起。 此上下文信息的一个常见示例是作为更改源的数据存储区的标识符。

若要执行上述操作,同步应用程序可使用下列函数:

  • CHANGETABLE(VERSION…)

    当应用程序进行更改时,它可以使用该函数来检查冲突。 对于启用了更改跟踪的表,该函数可获取该表中指定行的最新更改跟踪信息。 更改跟踪信息包括上次更改的行的版本。 应用程序可以使用此信息来确定自上次应用程序同步后该行是否进行了更改。

  • WITH CHANGE_TRACKING_CONTEXT

    应用程序可以使用此子句来存储上下文数据。

检查冲突

在双向同步方案中,客户端应用程序必须确定在应用程序上次获取更改后某一行是否有更新。

下面的示例说明了如何使用 CHANGETABLE(VERSION …) 函数以最有效的方式检查冲突,而不是使用单独的查询。 在该示例中,CHANGETABLE(VERSION …) 确定 @product id 所指定的行的 SYS_CHANGE_VERSION。 CHANGETABLE(CHANGES …) 可以获取相同的信息,但效率较低。 如果该行的 SYS_CHANGE_VERSION 值大于 @last_sync_version 值,则说明有冲突。 如果有冲突,则不会更新该行。 ISNULL() 检查是必需的,因为该行可能没有可用的更改信息。 如果在启用更改跟踪或清除更改信息后没有更新该行,则不存在任何更改信息。

-- Assumption: @last_sync_version has been validated.

UPDATE
    SalesLT.Product
SET
    ListPrice = @new_listprice
FROM
    SalesLT.Product AS P
WHERE
    ProductID = @product_id AND
    @last_sync_version >= ISNULL (
        SELECT CT.SYS_CHANGE_VERSION
        FROM CHANGETABLE(VERSION SalesLT.Product,
                        (ProductID), (P.ProductID)) AS CT),
        0)

以下代码可以检查更新的行数以及找出有关冲突的更多信息。

-- If the change cannot be made, find out more information.
IF (@@ROWCOUNT = 0)
BEGIN
    -- Obtain the complete change information for the row.
    SELECT
        CT.SYS_CHANGE_VERSION, CT.SYS_CHANGE_CREATION_VERSION,
        CT.SYS_CHANGE_OPERATION, CT.SYS_CHANGE_COLUMNS
    FROM
        CHANGETABLE(CHANGES SalesLT.Product, @last_sync_version) AS CT
    WHERE
        CT.ProductID = @product_id;

    -- Check CT.SYS_CHANGE_VERSION to verify that it really was a conflict.
    -- Check CT.SYS_CHANGE_OPERATION to determine the type of conflict:
    -- update-update or update-delete.
    -- The row that is specified by @product_id might no longer exist 
    -- if it has been deleted.
END

设置上下文信息

通过使用 WITH CHANGE_TRACKING_CONTEXT 子句,应用程序可以将上下文信息与更改信息存储在一起。 可随后从 CHANGETABLE(CHANGES …) 返回的 SYS_CHANGE_CONTEXT 列中获取此信息。

上下文信息通常用于确定更改源。 如果可以确定更改源,数据存储区在重新同步时可使用该信息来避免获取更改。

  -- Try to update the row and check for a conflict.
  WITH CHANGE_TRACKING_CONTEXT (@source_id)
  UPDATE
     SalesLT.Product
  SET
      ListPrice = @new_listprice
  FROM
      SalesLT.Product AS P
  WHERE
     ProductID = @product_id AND
     @last_sync_version >= ISNULL (
         (SELECT CT.SYS_CHANGE_VERSION FROM CHANGETABLE(VERSION SalesLT.Product,
         (ProductID), (P.ProductID)) AS CT),
         0)

确保获得一致且正确的结果

在验证 @last_sync_version 值时,应用程序必须考虑清除过程。 这是因为,在调用 CHANGE_TRACKING_MIN_VALID_VERSION() 后但在进行更新之前,可能已将数据删除。

重要说明重要提示

建议您使用快照隔离,并在快照事务中进行更改。

-- Prerequisite is to ensure ALLOW_SNAPSHOT_ISOLATION is ON for the database.

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRAN
    -- Verify that last_sync_version is valid.
    IF (@last_sync_version <
CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(‘SalesLT.Product’)))
    BEGIN
       RAISERROR (N’Last_sync_version too old’, 16, -1);
    END
    ELSE
    BEGIN
        -- Try to update the row.
        -- Check @@ROWCOUNT and check for a conflict.
    END
COMMIT TRAN
注意注意

在快照事务启动之后,在该快照事务中正在更新的行可能已经在另一个事务中进行了更新。 在这种情况下,会发生快照隔离更新冲突并导致该事务终止。 如果发生这种情况,请重试此更新。 随后,这会导致检测到更改跟踪冲突并且不会更改任何行。