數據庫內核月報-2017年05期-最佳實踐 MSSQLServer2008數據增量同步

最佳實踐 MSSQLServer2008數據增量同步

問題來源

項目組要實現兩個非同構數據庫的數據同步功能,爲滿足時間要求,最好是支持增量同步。

方案決策

  • 使用數據庫自帶的發佈訂閱實現
    因爲源庫和目標庫不同構,實現起來比較複雜,並且客戶環境的網絡不好,在以前的使用案例中,容易出現隊列阻塞或線程宕掉的情況。
  • 利用數據庫的特性實現
    利用數據庫特性,性能上有一定的保障。實現簡單,開發效率高。
  • 開發獨立程序實現
    充分利用JAVA讀取寫入多線程,併發寫入步提高性能。開發難度高,多線程編程有一定的難度。
  • 使用開源工具實現
    採用kettle,方案成熟,在該客戶的其他項目中,有成功案例,未知風險較小。但配置複雜,學習成本高,工期較長。

綜上所述,採用利用數據庫的特性實現,性能上有保障,開發工期上有保障。

方法與原理

  • 統一入口
    提供統一入口,創建維護計劃定期調用,在該入口中順序調用業務存儲過程
  • 拆分業務集
    根據業務拆分表(幾十張)到不同業務存儲過程(可多人同時開發不會衝突,方便管理)
    同一個數據庫(分別涉及到三個數據庫)相關的存儲過程使用同一個事物。若無異常則提交事務並記錄本數據庫增量記錄表記錄;若出現異常則回滾事物,並記錄錯誤日誌到日誌表,並直接處理下一個數據庫相關過程
  • 具體同步操作
    每個業務存儲過程需要傳入開始、截止時間戳,以及傳出當前步驟、結果代碼、結果信息,共五個參數。開始及截止時間戳用於過濾出本次應該處理的結果集範圍;爲方便調試,在操作每個表之前先記錄當前步驟,遇到異常時,把錯誤代碼及錯誤明細填充至結果代碼和結果信息中,然後退出本過程,外部獲取當前步驟及錯誤代碼、錯誤信息,保存至錯誤日誌表,供後續查詢。
    每個表根據傳入的時間戳過濾出本次需要處理的結果集,再結合MERGE的特性同步到目標表

技術難點及解決

  • 如何發現增量

    該系統屬於已開發上線的系統,未進行增量設計。MSSQL的特性——時間戳,能自動記錄變化版本。
    TIMESTAMP-數據庫中自動生成的唯一二進制數字,與時間和日期無關,通常用作給錶行加版本戳的機制。存儲大小爲 8個字節)。
    每個表最多隻允許有一列定義爲TIMESTAMP類型,並且不允許修改其內容。每個數據庫都有一個計數器,當對包含TIMESTAMP列的表執行插入或更新操作時,該計數器值就會增加,並且自動更新對應記錄

--創建測試表
CREATE TABLE T1
(
N_ID INT IDENTITY,
C_NAME VARCHAR(300),
B_ROWVERSION TIMESTAMP 
)
--插入記錄 我們這裏只對第二個字段進行處理,看看結果如何
INSERT INTO T1 (C_NAME) VALUES ('張三')
INSERT INTO T1 (C_NAME) VALUES ('李四')

--查看結果
SELECT * FROM T1
--第一列是自增列,第三列是時間戳列,他們都自動生成了數據
N_ID    C_NAME    B_ROWVERSION
1    張三    0x00000000000DC0D2
2    李四    0x00000000000DC0D3

--查看本數據庫當前版本號
SELECT @@DBTS --結果 0x00000000000DC0D3

--我們更新一下張三對應的記錄
UPDATE T1 SET C_NAME = '王五' WHERE N_ID = 1

--再次查看結果
SELECT * FROM T1
--每個表的TIMESTAMP類型列是由系統自動維護的,不允許手動更改
N_ID    C_NAME    B_ROWVERSION
1    王五    0x00000000000DC0D4
2    李四    0x00000000000DC0D3
  • 如何匹配更新源庫與目標庫記錄

    除了先刪再插,以及先查詢再確定應該更新或插入,MSSQL還提供了另一個特性:MERGE關鍵字。該關鍵字可以根據與源表聯接的結果,對目標表執行插入、更新或刪除操作

--創建測試表1
CREATE TABLE T1
(
N_ID INT,
C_NAME VARCHAR(300),
B_ROWVERSION TIMESTAMP 
)
--創建測試表2
CREATE TABLE T2
(
N_ID INT,
C_NAME VARCHAR(300),
B_ROWVERSION TIMESTAMP 
)

--插入記錄
INSERT INTO T1 (N_ID, C_NAME) VALUES (1, '張三')
INSERT INTO T1 (N_ID, C_NAME) VALUES (2, '李四')

INSERT INTO T2 (N_ID, C_NAME) VALUES (1, '王五')
INSERT INTO T2 (N_ID, C_NAME) VALUES (3, '馬六')

--T1當前結果
N_ID    C_NAME    B_ROWVERSION
1    張三    0x00000000000DC0F6
2    李四    0x00000000000DC0F7
--T2當前結果
N_ID    C_NAME    B_ROWVERSION
1    王五    0x00000000000DC0F8
3    馬六    0x00000000000DC0F9

MERGE T1 AS T
USING (SELECT N_ID, C_NAME FROM T2) AS S
ON (T.N_ID = S.N_ID)
WHEN MATCHED THEN --兩邊可以匹配
  UPDATE SET C_NAME = S.C_NAME
WHEN NOT MATCHED BY TARGET THEN --目標表中不存在匹配
  INSERT (N_ID, C_NAME) VALUES (S.N_ID, S.C_NAME)
WHEN NOT MATCHED BY SOURCE THEN --源表中不存在匹配
  DELETE; 

--T1當前結果
N_ID    C_NAME    B_ROWVERSION
1    王五    0x00000000000DC110
3    馬六    0x00000000000DC111
--T2當前結果
N_ID    C_NAME    B_ROWVERSION
1    王五    0x00000000000DC0F8
3    馬六    0x00000000000DC0F9

增量同步僞代碼

  • 過程主入口
CREATE PROCEDURE [dbo].[PR_XXXX_SJSB_0_RK]
AS
BEGIN
    DECLARE @nResultCode INT  --結果代碼
    DECLARE @cResultMsg VARCHAR(900)  --結果信息
    DECLARE @bRowVersionBegin BINARY(8)  --開始時間戳
    DECLARE @bRowVersionEnd BINARY(8)  --結束時間戳
    DECLARE @cCurrProName VARCHAR(300)  --當前過程名稱
    DECLARE @cCurrStepName VARCHAR(300)  --當前步驟名稱

    --不顯示統計行信息
    SET NOCOUNT ON 

    --1. 創建數據上報所需的表
    --1.1 數據上報錯誤日誌表
    IF NOT EXISTS (SELECT 1 FROM DB_XXXX.DBO.SYSOBJECTS WHERE TYPE = 'U' AND NAME = 'T_SJSB_CWRZ')
    BEGIN
      CREATE TABLE DB_XXXX.DBO.T_SJSB_CWRZ(
        N_BH INT NOT NULL IDENTITY, --主鍵
        C_GCMC VARCHAR(300) NOT NULL, --過程名稱
        C_BZMC VARCHAR(300) NOT NULL, --步驟名稱
        N_CWDM INT NOT NULL, --錯誤代碼
        C_CWMX VARCHAR(900), --錯誤明細
        DT_GXSJ DATETIME, --更新時間
        PRIMARY KEY (N_BH)
      )
    END 
    ...

    --2. 處理AA庫所有表
    --2.1 獲取AA庫時間戳
    EXECUTE DB_AA.DBO.PR_AA_SJSB_HQSJC @bRowVersionBegin OUTPUT, @bRowVersionEnd OUTPUT
    --2.2 開始調用相關存儲過程
    --開啓事物
    SET XACT_ABORT ON
    BEGIN DISTRIBUTED TRANSACTION

    SET @nResultCode = 0
    IF @nResultCode = 0
    BEGIN
        --2.2.1 AA基本信息
        SET @cCurrProName = 'PR_XXXX_SJSB_1_AAJBXX'
        EXECUTE DB_XXXX.DBO.PR_XXXX_SJSB_1_AAJBXX @bRowVersionBegin, @bRowVersionEnd, @cCurrStepName OUTPUT, @nResultCode OUTPUT, @cResultMsg OUTPUT
        PRINT @nResultCode
    END
    IF @nResultCode = 0
    BEGIN
        --2.2.2 AA綜合信息
        SET @cCurrProName = 'PR_XXXX_SJSB_2_AAZH'
        EXECUTE DB_XXXX.DBO.PR_XXXX_SJSB_2_AAZH @bRowVersionBegin, @bRowVersionEnd, @cCurrStepName OUTPUT, @nResultCode OUTPUT, @cResultMsg OUTPUT
        PRINT @nResultCode
    END
    --插入增量記錄表記錄
    IF @nResultCode = 0
        INSERT INTO DB_XXXX.DBO.T_SJSB_ZLJL (C_KM, B_SJC, DT_GXSJ) VALUES ('DB_AA', @bRowVersionEnd, GETDATE())

    --提交事物
    PRINT @nResultCode
    IF @nResultCode = 0
      COMMIT TRAN
    ELSE
    BEGIN
      ROLLBACK TRAN
      INSERT INTO DB_XXXX.DBO.T_SJSB_CWRZ (C_GCMC, C_BZMC, N_CWDM, C_CWMX, DT_GXSJ)
      VALUES (@cCurrProName, @cCurrStepName, @nResultCode, @cResultMsg, GETDATE())
    END


    --3. 處理BB庫所有表
    ...
    --4. 處理CC庫所有表
    ...

    SET XACT_ABORT OFF
    SET NOCOUNT OFF
    --過程結束
END
  • 業務過程
CREATE PROCEDURE [dbo].[PR_XXXX_SJSB_1_AAJBXX](
  @bRowVersionBegin BINARY(8),
  @bRowVersionEnd BINARY(8),
  @cCurrStepName VARCHAR(300) OUTPUT,
  @nResultCode INT OUTPUT,
  @cResultMsg VARCHAR(900) OUTPUT
)
AS
BEGIN
  SET @nResultCode = 0
  BEGIN TRY
    --基本信息
    SET @cCurrStepName = 'DA_JBXX1'
    --禁用自增列
    SET IDENTITY_INSERT XXXX.DBO.DA_JBXX ON

    EXECUTE DB_XXXX.DBO.PR_XXXX_SJSB_SCBHYS 'DB_ZF.DBO.T_ZF', @bRowVersionBegin, @bRowVersionEnd
    MERGE XXXX.dbo.DA_JBXX AS T
    USING (SELECT DB_XXXX.DBO.FUN_GET_ID_BY_SOURCE('DB_AA.DBO.T_AA', ZF.C_ID) ID,
    ISNULL(AA.C_ZFBH, '') C_ZFBH, ISNULL(AA.C_XM, '') C_XM, ...

    ISNULL(AA.DT_CreateTime, '') DT_CreateTime FROM DB_AA.DBO.T_AA AA
    WHERE AA.B_RowVersion > @bRowVersionBegin AND ZF.B_RowVersion <= @bRowVersionEnd
    ) AS S
    ON (T.ID = S.ID)
    WHEN MATCHED THEN
    UPDATE SET XM = S.C_XM, ... LRSJ = S.DT_CreateTime
    WHEN NOT MATCHED BY TARGET THEN
    INSERT (ID, BH, XM, ...LRSJ) VALUES (S.ID, S.C_ZFBH, S.C_XM, ... S.DT_CreateTime);

      --恢復自增列屬性
      SET IDENTITY_INSERT XXXX.DBO.DA_JBXX OFF

      --AA別名化名
      SET @cCurrStepName = 'DA_BHM'
      --禁用自增列
      SET IDENTITY_INSERT XXXX.DBO.DA_BHM ON
      EXECUTE DB_XXXX.DBO.PR_XXXX_SJSB_SCBHYS 'DB_AA.dbo.T_AA_BMHM', @bRowVersionBegin, @bRowVersionEnd
      MERGE XXXX.dbo.DA_BHM AS T
      USING (SELECT DB_XXXX.DBO.FUN_GET_ID_BY_SOURCE('DB_AA.dbo.T_AA_BMHM', BMHM.C_ID) ID,
      AA.C_ZFBH, ISNULL(BMHM.C_BHM,'') C_BHM,
      ISNULL(BMHM.DT_CreateTime, '') DT_CreateTime
      FROM DB_ZF.dbo.T_AA AA
      INNER JOIN DB_AA.dbo.T_AA_BMHM BMHM ON AA.C_ID = BMHM.C_AA_ID
      WHERE AA.C_ID IS NOT NULL  
      AND BMHM.B_RowVersion > @bRowVersionBegin AND BMHM.B_RowVersion <= @bRowVersionEnd
        ) AS S
      ON (T.ID = S.ID)
      WHEN MATCHED THEN
        UPDATE SET BH = S.C_ZFBH, BHM = S.C_BHM, LRSJ = S.DT_CREATETIME
      WHEN NOT MATCHED BY TARGET THEN
        INSERT (ID, BH, BHM, LRSJ) 
        VALUES (S.ID, S.C_ZFBH, S.C_BHM, S.DT_CREATETIME);

      --恢復自增列屬性
      SET IDENTITY_INSERT XXXX.DBO.DA_BHM OFF
    ...

   END TRY

   BEGIN CATCH
     SET @nResultCode = ERROR_NUMBER()
     SET @cResultMsg = ERROR_MESSAGE()
   END CATCH       
END

結語

上面我們簡單介紹了MSSQL中的TIMESTAMP以及MERGE的用法。利用這兩個特性,我們可以很方便的實現兩個庫之間的增量同步操作,節省工期,提高效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章