變更追蹤如何處理資料庫的變更

某些使用變更追蹤的應用程式會使用另一個資料存放區來執行雙向同步處理。也就是說,在 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

[!附註]

當快照集交易啟動之後,快照集交易內所更新的資料列有可能已經在另一個交易內更新。在此情況下,將會發生快照集隔離更新衝突,而且會導致交易結束。如果發生這種情況,請重試更新。然後,這會讓變更追蹤衝突得以偵測到,而且不會變更任何資料列。