MySQL案例-並行複製亂序提交引起的同步異常

wangwenan6發表於2018-04-19

現象描述

Slave在開啟並行複製後, 預設會亂序提交事務, 可能會引起同步中斷;

Slave端表現為同步的SQL執行緒丟擲異常, 為主鍵重複, 修改的資料行不存在等;

GTID資訊類似於: 9a2a50aa-5504-11e7-9e59-246e965d93f4:1-1371939844:1371939846


其中1371939845為報錯的事務, 直觀上看, Slave端先提交了1371939846事務;


解決辦法

MySQLversion>=5.7.5
slave_preserve_commit_order:OFF(default)->ON
注:binlog_order_commits=ON(default)


問題分析

參考官方的WL#6314WL#7165, 這裡對原文內容進行簡單的歸納, 有興趣的可以看看原文的High Level Architecture;
WL#6314 : https://dev.mysql.com/worklog/task/?id=6314
WL#7165 : https://dev.mysql.com/worklog/task/?id=7165
注: 英文原文中的commit-parent transaction, sequence number指的就是binlog中的last_commited和sequence_number; 即簡單翻譯中的”邏輯時間戳標記” 


WL#6314 關於slave端的並行applier

當事務進入prepare階段(組提交流程的某一個階段)時, 這些事務都會獲得一個邏輯時間戳的標記, 用來標記最新提交的事務是哪個;

在master端, 有關流程如下:

  • 在prepare階段, 從commit_clock中獲取時間戳並儲存下來, 用來標記最新提交的事務;
  • 在commit階段(事務已經寫入binlog, 但是在引擎層提交前), 對commit_clock執行步進操作;

在Slave端, 有關流程如下:

  • coordinate執行緒會讀取relaylog的event, 如果這些event都有相同的邏輯時間戳(last_commited), 那麼這些event就可以由worker並行執行;

WL#7165 有關並行複製的並行度優化

參照WL#6314的描述, 雖然已經實現了並行複製, 但是並沒有達到預期的程度;

舉例: 下圖代表各個事務的執行順序與時間線, 其中P代表單個事務的prepare階段, 在這個階段會獲取到commit_clock的時間戳, C代表這個事務的寫binlog的階段, 在這裡會對commit_clock進行步進操作;

如上圖所示, Trx1, Trx2, Trx3的P階段獲取到的都是同一個last_commited值(比如說是1), 因此這三個事務可以在Slave端並行執行; 同理, Trx4不能和< Trx1, Trx2, Trx3 > 一起並行回放, 因為Trx4的P階段, 獲取到的last_commited值是Trx1執行完步進以後的值(步進之後變成了2); 

按照WL#6314的邏輯, Slave端可以發現這七個事務分成了四個事務組, 分別是< Trx1, Trx2, Trx3 >, < Trx4 >, < Trx5, Trx6 >, < Trx7 >;

但是需要注意的是, 對於不同的事務組, < Trx4 > 和 < Trx5, Trx6 > 是能併發執行的, 因為從時間線上看, < Trx4 > 和 < Trx5, Trx6 > 的prepare階段在時間線上是有重疊的, 這也就意味著這兩組事務並不存在鎖的衝突, 那麼就可以在Slave並行執行;


對於並行度的優化

改進後的並行複製使用鎖來判斷是否可以進行併發;

基本邏輯如下:

L代表鎖階段開始, C代表鎖階段結束;

A中的Trx1和Trx2由於鎖階段存在重合, 也沒有發生衝突, 說明Trx1和Trx2是可以並行執行的, 但是B不行, 因為Trx1和Trx2的鎖階段沒有重合, 所以無法確認是不是可以並行執行(不做額外的判斷, 直接當做不可並行處理, 節約效能開銷);

關於鎖階段的判斷, WL中明確表示沒有進行鎖分析, 而是直接把事務提交的一些階段作為加鎖與釋放鎖的時間點(從事務提交的階段來看, 也沒什麼問題);

  • 假設在進行儲存引擎層的提交之前, 所有的鎖都已已經釋放(鎖階段結束的時間點);
  • 假設在prepare階段開始的時候, 所有需要的鎖已經全部獲取到(鎖階段開始的時間點);


在MySQL的binlog中, L所指的標記就是last_commited, C所指的標記就是sequence_number;

關於last_commited和sequence_number, WL#7165有做如下描述

  • 在事務進入flush階段前, 會步進transaction.sequence_number的值 –> 顯示為sequence_number
  • 在事務進入引擎層提交之前, 會修改 global.max_committed_transaction的值 
    • = max(global.max_committed_timestamp, transaction.sequence_number) 
    • = transaction.sequence_number (如果binlog_order_commits使用預設值ON)


因此, Slave端在決定SQL是否可以併發執行時, 參考如下原則:

-----------------------------------------------------------------------------------------------------------
Slave can execute a transactionifthe smallest sequence_number 
    among all executing transactions is greater than transaction.last_committed.
-----------------------------------------------------------------------------------------------------------

虛擬碼會更直觀一些:
-----------------------------------------------------------------------------------------------------------
Slave logic:
    -before scheduler pushes the transaction for execution : wait until         transaction_sequence[0].sequence_number>transaction.last_committed
-----------------------------------------------------------------------------------------------------------

所以使用基於鎖的並行度優化後, 確實可以讓WL#6314的< Trx4 > 和 < Trx5, Trx6 > 併發執行;


故障場景還原

Slave上報錯的事務為1371939845, binlog內容如下, 事務缺少1371939845; 

Master上的事務序列如下: 


參考WL#6314的格式, 根據Master的事務序列繪製事務序列圖, GTID, last_commited, sequence_number均使用最後兩位數作為標記;

由於Slave是亂序提交的, 所以這些事務在Slave的binlog中並非嚴格按照GTID遞增的順序出現



根據WL#7165的描述, 可以得出: 在Slave上, 當Trx41執行完畢之後, Slave認為, Trx46與Trx47已經可以由coordinate進行排程, 與< Trx42, Trx43, Trx44, Trx45 > 並行執行了, 但是Trx45與Trx46, Trx47 存在業務上的先後順序(且確實存在鎖衝突), 所以先執行的Trx46刪除了Trx45需要的資料, 導致同步中斷;

PS: 既然Trx45和Trx46有鎖衝突, 為什麼Trx46會拿到84作為last_commited, 而不是88?


參考WL#7165的虛擬碼,
-----------------------------------------------------------------------------------------------------------
When@@global.binlog_order_commitsistrue,inprinciple we could reduce the max
    to an assignment:
     global.max_committed_transaction=transaction.sequence_number
-----------------------------------------------------------------------------------------------------------
MySQL-5.7.21的原始碼:
MYSQL_BIN_LOG::ordered_commit-->
process_commit_stage_queue-->
update_max_committed
-----------------------------------------------------------------------------------------------------------

因此推測主庫當時候是如下場景:<Trx35~Trx45>作為一個事務組,進入到了儲存引擎的commit階段前,會遞增sequence_number,而不是一次到位的全部加上;

所以Trx46進入prepare階段時,剛好是Trx41完成了commit階段,所以拿到的是84,而不是88;雖然官方描述中,認為會達到最終一致的狀態,但是同步過程中會存在短暫的不一致現象,這種現象被描述為"GAP";



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29510932/viewspace-2153094/,如需轉載,請註明出處,否則將追究法律責任。

相關文章