1. 程式人生 > >MySQL資料庫分散式事務XA優缺點與改進方案

MySQL資料庫分散式事務XA優缺點與改進方案

1 MySQL 外部XA分析

1.1 作用分析

  MySQL資料庫外部XA可以用在分散式資料庫代理層,實現對MySQL資料庫的分散式事務支援,例如開源的代理工具:ameoba[4],網易的DDB,淘寶的TDDL,B2B的Cobar等等。

  通過MySQL資料庫外部XA,這些工具可以提供跨庫的分散式事務。當然,這些工具也就成了外部XA事務的協調者角色。在crash recover時控制懸掛事務是全域性commit,或者rollback。

  在crash recover之後,外部應用程式可能會遇到以下幾種情況:

  • 情況一:分散式事務對應的MySQL資料庫例項,部分完成prepare,部分未完成prepare。此時直接回滾完成prepare的例項即可。n_prepared < Total Nodes (處於prepare狀態的節點數量要小於參與分散式事務的所有節點總數)。
  • 情況二:分散式事務對應的MySQL例項,全部完成prepare,未開始進行commit。此時即可提交此事務,也可回滾此事務(根據分散式事務原理,所有節點都完成prepare,應該提交)。n_prepared = Total Nodes。
  • 情況三:分散式事務對應的MySQL例項,全部完成prepare,並且部分節點已經完成commit。此時應該提交該事務處於prepare狀態的節點。n_prepared < Total Nodes。對比情況三與情況一,僅僅通過prepare節點的數量無法區分,因此應用程式需要在prepare完成之後記錄日誌(此時,應用程式起著事務協調者(Transcaction Coordinator)的角色,而根據MariaDB WorkLog#132[5]的說法,TC角色是可以進行”middle engine”優化的,不需要prepare過程,所有MySQL節點xa prepare返回之後,應用程式直接寫commit標識即可,然後再對每個MySQL節點進行xa commit操作。),從而用於區分情況一與情況三。
  • 情況四:分散式事務對應的MySQL例項,全部完成commit。此時事務已經提交成功,xid不會出現在執行xa recover的任一個節點。不需要特殊處理。
  • 情況五:未記錄任何prepare日誌。那麼所有的事務,在各個儲存引擎的crash recover時,都會被回滾,不需要外部特殊處理。

1.2 MySQL外部XA不足

  通過前面的分析,可知應用程式配合MySQL的XA事務功能,能夠較好的支援分散式環境下的事務。但是,這個支援並不完美,根據我的分析,有可能會出現以下幾個問題:

  l 問題一:主備資料庫的資料不一致。

  MySQL資料庫的主備資料庫的同步,通過Binlog的複製完成。而Binlog是MySQL資料庫內部XA事務的協調者,並且MySQL資料庫為binlog做了優化——binlog不寫prepare日誌,只寫commit日誌。

  考慮前面提到的情況二,所有的參與節點prepare完成,在進行xa commit前crash。crash recover如果選擇commit此事務。由於binlog在prepare階段未寫,因此主庫中看來,此分散式事務最終提交了,但是此事務的操作並未寫到binlog中,因此也就未能成功複製到備庫,從而導致主備庫資料不一致的情況出現。

  在MySQL 5.5.16版本中做過測試,這個問題實際存在。crash recover之後,對xa recover返回的事務執行xa commit,對應事務提交,但是操作並未寫入binlog,因此無法複製到備庫。

  那麼是否回滾所有prepare的事務,就可以避免此問題呢?結論是仍舊不行,不僅不能解決問題一,甚至可能引起問題二。

  l 問題二:同一事務,在各參與節點,最終狀態不一致(部分提交,部分回滾)。

  若回滾所有prepare狀態的分散式事務,會產生問題二。考慮情況三(所有節點完成prepare,部分節點完成commit),該分散式事務對應的節點,部分已經提交,無法回滾,而部分節點回滾。最終導致同一分散式事務,在各參與節點,最終狀態不一致。

  l 問題三:原始碼級別問題。MySQL 5.1.49原始碼對於外部XA事務處理存在bug,在MySQL 5.5.16版本中,此bug已經被fix。經過驗證發現,在我已下載的MySQL 5.1.61與之後的所有版本,此bug均已經被fix。

  在MySQL 5.1.49中,所有xa recover返回的外部xid,都不能被提交。原因如下:

  當執行xa commit ‘xid_name’命令時,MySQL會判斷當前xid_name的錯誤資訊,若存在錯誤資訊,那麼就在內部將xa commit命令強制轉換為xa rollback。xid_name的狀態存於xid_cache中,在crash recover階段,由函式Handler.cc::xarecover_handlerton呼叫xid_cache_insert(&xid_cache, x)函式完成插入。MySQL 5.1.49在實現xid_cache_insert函式有bug。

  …

  xs->xa_state=xa_state;

  xs->xid.set(xid);

  xs->in_thd=0;

  xs->rm_error=0;

  res=my_hash_insert(&xid_cache, (uchar*)xs);

  …

  MySQL 5.1.49中,缺少了xs->rm_error =0這一行,未初始化rm_error,導致xa commit時判斷出錯,無法commit。MySQL 5.5.16已經fix此bug,加上了黑色這一行的初始化,應用程式可以xa commit。

1.3 不足的解決方案

  從MySQL資料庫外部分散式事務XA不足的分析可以看出,除了實現bug之外,產生其餘兩個問題的最大原因,還是在於MySQL針對binlog做的 ”middle engine” 優化,binlog的prepare不寫日誌。在MySQL內部XA事務中,這個優化是可行的,因為Binlog本身的角色就是事務協調者(Transaction Coordinator),事務協調者可以不進行prepare [5]。

  但是對於MySQL外部XA事務,Binlog已經不是事務協調者的角色,其也是一個參與者,或者說是Resource Manager。因此Binlog的prepare日誌是不可省略的。

  為了解決MySQL資料庫外部XA分散式事務crash recover過程中出現的問題,我覺得只能修改binlog模組。使binlog模組在正常執行過程中也區分內部XA事務與外部XA事務。內部XA事務可以仍舊沿用現在的方案;而外部XA分散式事務,需要增加寫prepare日誌的功能,已經crash recover時處理prepare日誌的功能。

2 參考資料

  [1] Sergei Golubchik. Distributed Transaction Processing with MySQL XA

  [2] http://dev.mysql.com/doc/refman/5.1/en/xa.html

  [3] X/Open. Distributed TP: The XA Specification

  [4] 陳思儒. Amoeba

  [5] MariaDB WorkLog: Transaction coordinator plugin