Replication的犄角旮旯(九)-- sp_setsubscriptionxactseqno,賦予訂閱活力的工具


《Replication的犄角旮旯》系列導讀

Replication的犄角旮旯(一)--變更訂閱端表名的應用場景

Replication的犄角旮旯(二)--尋找訂閱端丟失的記錄

Replication的犄角旮旯(三)--聊聊@bitmap

Replication的犄角旮旯(四)--關於事務復制的監控

Replication的犄角旮旯(五)--關於復制identity列

Replication的犄角旮旯(六)-- 一個DDL引發的血案(上)(如何近似估算DDL操作進度)

Replication的犄角旮旯(七)-- 一個DDL引發的血案(下)(聊聊logreader的延遲)

Replication的犄角旮旯(八)-- 訂閱與發布異構的問題

Replication的犄角旮旯(九)-- sp_setsubscriptionxactseqno,賦予訂閱活力的工具

 

 

---------------------------------------華麗麗的分割線--------------------------------------------

前言:有人總是拿Mysql的Master\Slave和SQL Server的replication比較,說Mysql的復制有多么強大、多么靈活。作為SQLServer的死忠,也曾被replication各種 的黑盒搞得體無完膚。不過還好,我們還是能從MS流露出來的各種存儲過程中,發現蛛絲馬跡,結合我們的頭腦風暴,來一場真真正正的革命……

sp_setsubscriptionxactseqno,第一次了解是在拜讀前任DBR的blog《在SQL Server 2005/2008事務復制中如何跳過一個事務》時,而近期在處理一個復制異常事件時,忽然靈光閃現,既然可以向后跳過某些事務,是否可以前滾到之前的某個時間點再繼續復制呢?本文將通過實際測試,繼續玩復制

 

閑話少敘,書歸正傳……

 

關於跳過某些復制事務,在此不再贅述,詳見《在SQL Server 2005/2008事務復制中如何跳過一個事務》;這里只說如何追溯到之前某個時間點;

關於sp_setsubscriptionxactseqno這個存儲過程,詳見MSDN:https://msdn.microsoft.com/zh-cn/library/ms188764.aspx

具體用法如下:

sp_setsubscriptionxactseqno [ @publisher = ] 'publisher' 
        , [ @publisher_db = ] 'publisher_db' 
        , [ @publication = ] 'publication' 
        , [ @xact_seqno = ] xact_seqno

其中@xact_seqno這個參數,如果指定當前事務(起始點)之后的某個事務(截止點),就可以跳過兩個時間點之間的事務;但如果需要跳到當前事務之前的某個事務時,除了sp_setsubscriptionxactseqno這個存儲過程外,還需要一個系統表來配合才能實現——MSsubscriptions,位於分發庫(默認為distribution)中;

MSsubscriptions記錄了每個訂閱與發布項目的關系,詳見MSDN:https://msdn.microsoft.com/zh-cn/library/ms188368.aspx

其中publisher_seqno表示該訂閱創建時在發布服務器上的事務序列號subscription_seqno為快照事務序列號(非初始化訂閱則與publisher_seqno一致);

在事務復制中,訂閱端應用事務時,需要檢測當前事務是否小於這兩個參數,如果當前事務號小於上述兩個列的值,則邏輯上判為不成立(當前執行的事務早於創建訂閱時的事務,理論上不成立)。

因此,要想回跳到之前某個時間點的事務,需要手動更新相應的記錄,至少保證publisher_seqno和subscription_seqno與你要回跳的那個事務號一致;

至此,我們目的達成,但這又有什么意義呢?當前時間點下的數據為什么要回跳到之前的某個事務呢?這就要說到前幾天我們處理的一個案例;

 

先說一下我們的復制環境,以下圖為例:

根節點為寫庫,承載主寫業務,為避免單點故障,增加一災備節點進行保護;

轉發節點作為根節點的訂閱以及末端節點的發布,只進行復制命令的轉發工作,雙轉發進行冗余,避免單鏈路故障影響末端訂閱;

末端訂閱承載各類讀業務,雙鏈路各取部分節點做負載均衡;

 

看似健壯的架構,避免的單點問題。但仍有個隱患,當任意一個轉發節點故障時,盡管可以隨時刪除復制鏈路(末端訂閱有負載均衡保護,隨時可以刪除節點),但由於復制的基礎數據過大,故障回復時,無論是快照還是備份初始化,時間成本都很高。怎么破?

此前出現了"轉發節點02"因增加存儲空間導致開機后出現邏輯頁錯誤,致使該條鏈路失效,難道真的需要重新初始化才能恢復?(需要重建3個節點,轉發節點02、末端訂閱03、04)

或許我們有更好的辦法可以避免初始化的過程;

以下為本地測試的情況:

 

思路:啟用災備節點作為轉發節點的基礎數據

難點:

1、災備節點數據如何恢復到故障時完整銜接中斷的復制事務?

2、如果能保證事務完美銜接但數據超前,如何處理redo部分的沖突,並使其正常分發到下級訂閱?

 

解決方法:

1、利用現有災備節點,可以快速實現數據超前於故障時間點;

2、通過sp_setsubscriptionxactseqno可以重置災備節點的訂閱事務號,使其從較早的事務進行redo;但同時需要修改[distribution].[dbo].[MSsubscriptions]中的publisher_seqnosubscription_seqno,使其生效;

3、由於災備節點數據超前於下級訂閱節點數據,需考慮對災備節點及下級訂閱節點中,訂閱表存儲過程的修正,實現自動redo的過程;

 

測試環境:

    根節點:BJYW-XIAOLEI\SQL01  testDB_A(SQL2012)

 轉發節點:BJYW-XIAOLEI\SQL02  testDB_A(SQL2008 R2)

 末端訂閱:BJYW-XIAOLEI\SQL02  testDB_B(SQL2008 R2)

 災備節點:BJYW-XIAOLEI\SQL01  testDB_C(SQL2012)

 

測試過程:

1、創建測試表及測試數據

 1 USE [testDB_A]
 2 
 3 GO
 4 
 5 CREATE TABLE [dbo].[test_a](
 6 
 7 [id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
 8 
 9 [context] [varchar](100) NULL,
10 
11 PRIMARY KEY CLUSTERED
12 
13 (
14 
15 [id] ASC
16 
17 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
18 
19 ) ON [PRIMARY]
20 
21 GO
22 
23 INSERT INTO dbo.test_a( context )VALUES  ( '00001' )
24 
25 GO 10
View Code

2、創建復制鏈路

      BJYW-XIAOLEI\SQL01  testDB_A  -->  BJYW-XIAOLEI\SQL02   testDB_A  -->  BJYW-XIAOLEI\SQL02  testDB_B      

      災備:BJYW-XIAOLEI\SQL01  testDB_C(SQL2012)使用同樣的測試腳本及數據;

      復制關系如下:

 

3、模擬故障

       在轉發節點(BJYW-XIAOLEI\SQL02   testDB_A)刪除一條數據后,在根節點上更新該數據

       轉發節點:DELETE testDB_A.dbo.test_a WHERE id=10

       根節點UPDATE testDB_A.dbo.test_a SET context='delete' WHERE id=10

 

      檢查msrepl_errors,發現報錯

 

4、主寫和災備節點再插入10條數據,用於模擬故障發生后,災備節點數據超前於末端訂閱節點

 
1 INSERT INTO dbo.test_a( context )VALUES  ( '00002' )
2 GO 10
View Code

 

5、創建主寫節點到災備節點的不初始化訂閱關系,並停止分發代理作業;同時創建災備節點到末端訂閱的不初始化復制鏈路;

      SQL01testDB_A庫新建到災備節點(testDB_C)的不初始化訂閱

 

      SQL01testDB_C庫新建到末端訂閱(testDB_B)的不初始化訂閱

 

6、修改災備節點的初始訂閱事務號為故障點的事務號

      找到最早出錯的事務號(見第3步):0x0000004300000040000300000000

 

 1 USE testDB_C
 2 GO
 3 
 4 sp_setsubscriptionxactseqno  @publisher =  'BJYW-XIAOLEI\SQL01'
 5         ,  @publisher_db =  'testdb_a'
 6         ,  @publication =  'SQL01_A'
 7         ,  @xact_seqno =  0x0000004300000040000300000000
 8 
 9 USE distribution
10 GO
11 
12 SELECT SP.publisher_seqno,SP.subscription_seqno,* FROM [distribution].[dbo].[MSsubscriptions] SP JOIN dbo.MSpublications PB ON SP.publisher_id=PB.publisher_id
13 AND SP.publisher_db=PB.publisher_db AND SP.publication_id=PB.publication_id
14 WHERE PB.publisher_db='testDB_A' AND pb.publication='SQL01_A' AND sp.subscriber_db='testDB_C'
15 
16  
17 
18 UPDATE SP
19 SET SP.publisher_seqno= 0x0000004300000040000300000000 ,SP.subscription_seqno=0x0000004300000040000300000000
20 FROM [distribution].[dbo].[MSsubscriptions] SP JOIN dbo.MSpublications PB ON SP.publisher_id=PB.publisher_id
21 AND SP.publisher_db=PB.publisher_db AND SP.publication_id=PB.publication_id
22 WHERE PB.publisher_db='testDB_A' AND pb.publication='SQL01_A' AND sp.subscriber_db='testDB_C'
View Code

        修改后的記錄

  7、修改災備節點的訂閱存儲過程

      a)  insert:判斷主鍵是否存在,如存在,需刪除后再insert

      b)  update:對非主要鍵值,建議先set col=null,再set col='',最后再執行正確的update

      c)  delete :暫不處理,只記錄主鍵信息,后續處理;

      Inster存儲過程

 

       Update存儲過程

 8、修改末端訂閱的訂閱存儲過程

      a)  insert:判斷主鍵是否存在,如存在,需刪除后再insert

      b)  update:記錄不存在主鍵的記錄,后續從log表中補足;

      c)  delete :注釋判斷if @@rowcount=0的部分,不報錯即可;

 

9、為方便監控,創建相應的trigger抓取實際操作情況;

 1 create TRIGGER [dbo].[tri_del_test_a]
 2    ON  [dbo].[test_a]
 3    AFTER DELETE
 4 AS
 5 BEGIN
 6 SET NOCOUNT ON;
 7 INSERT INTO monitor.dbo.trigger_monitor_byxl(tbname,t_type,t_VALUE,checktime)
 8 SELECT 'test_a','delete','id= '+CAST(ID AS VARCHAR(10)),GETDATE() FROM DELETED
 9 END
10 
11   
12 
13 create TRIGGER [dbo].[tri_upd_test_a]
14    ON  [dbo].[test_a]
15    AFTER UPDATE
16 AS
17 BEGIN
18 SET NOCOUNT ON;
19 INSERT INTO monitor.dbo.trigger_monitor_byxl(tbname,t_type,t_VALUE,checktime)
20 SELECT 'test_a','update','id= '+CAST(ID AS VARCHAR(10))+'- context='+context,GETDATE() FROM DELETED
21 END
View Code

 

10、啟用災備節點對應的分發代理;

        由於之前中間節點采用delete的方式刪除了數據,此處為了末端訂閱恢復正常,手動insert原記錄;

        實際生產環境中,可以根據故障出現時暫停的事務,手動處理一個事務;后續事務正常應用到末端訂閱

 

 注意:

由於在將災備節點轉為訂閱節點過程中,創建訂閱存儲過程的命令會記錄到msrepl_commands中,因此,在跨過創建訂閱的事務時,會將存儲過程重置為初始狀態;

另一方面,由於寫庫不停機,在將災備節點轉為訂閱節點時至手動停止分發代理過程中,可能存在少量數據寫入,因而在存儲過程重置后,由於數據少量超前,會導致主鍵沖突(insert)的問題,而update可能丟失(set值前后一樣的情況,不會出現臟數據,則不會記錄到復制命令中);

 

建議:在將災備節點轉為訂閱節點時,代理計划部分改為“按需執行”


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM