最佳實踐 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的用法。利用這兩個特性,我們可以很方便的實現兩個庫之間的增量同步操作,節省工期,提高效率。