MySQL 5.7 完美的分散式交易支援

來源:互聯網
上載者:User

MySQL 5.7 完美的分散式交易支援

Two Phase Commit Protocol

分散式交易通常採用2PC協議,全稱Two Phase Commitment Protocol。該協議主要為瞭解決在分散式資料庫情境下,所有節點間資料一致性的問題。在分散式交易環境下,事務的提交會變得相對比較複雜,因為多個節點的存在,可能存在部分節點提交失敗的情況,即事務的ACID特性需要在各個資料庫執行個體中保證。總而言之,在分布式提交時,只要發生一個節點提交失敗,則所有的節點都不能提交,只有當所有節點都能提交時,整個分散式交易才允許被提交。

分散式交易通過2PC協議將提交分成兩個階段

  1. prepare;
  2. commit/rollback

第一階段的prepare只是用來詢問每個節點事務是否能提交,只有當得到所有節點的“許可”的情況下,第二階段的commit才能進行,否則就rollback。需要注意的是:prepare成功的事務,則必須全部提交。

MySQL分散式交易

一直以來,MySQL資料庫是支援分散式交易的,但是只能說是有限的支援,具體表現在:

  • 已經prepare的事務,在用戶端退出或者服務宕機的時候,2PC的事務會被復原
  • 在伺服器故障重啟提交後,相應的Binlog被丟失

上述問題存在於MySQL資料庫長達數十年的時間,直到MySQL-5.7.7版本,官方才修複了該問題。 雖然InnoSQL早已在5.5版本修複,但是對比官方的修複方案,我們真的做的沒有那麼的優雅 。下面將會詳細介紹下該問題的具體表現和官方修複方法,這裡分別採用官方MySQL-5.6.27版本(未修複)和MySQL-5.7.9版本(已修複)進行驗證。

先來看下存在的問題,我們先建立一個表如下:

create table t(
    id int auto_increment primary key,
    a int
)engine=innodb;

對於上述表,通過如下操作進行資料插入:

mysql> XA START 'mysql56';
mysql> INSERT INTO t VALUES(1,1);
mysql> XA END 'mysql56';
mysql> XA PREPARE 'mysql56'

通過上面的操作,使用者建立了一個分散式交易,並且prepare沒有返回錯誤,說明該分散式交易可以被提交。通過命令XA RECOVER查看顯示如下結果:

mysql> XA RECOVER;
+----------+--------------+--------------+---------+
| formatID | gtrid_length | bqual_length | data    |
+----------+--------------+--------------+---------+
| 1        | 7            | 0            | mysql56 |
+----------+--------------+--------------+---------+

若這時候使用者退出用戶端後重連,通過命令xa recover會發現剛才建立的2PC事務不見了。 即prepare成功的事務丟失了,不符合2PC協議規範!!!

產生上述問題的主要原因在於:MySQL-5.6版本在用戶端退出的時候,自動把已經prepare的交易回復了,那麼MySQL為什麼要這樣做?這主要取決於MySQL的內部實現,MySQL-5.7以前的版本,對於prepare的事務, MySQL是不會記錄binlog的 (官方說是減少fsync,起到了最佳化的作用)。只有當分散式交易提交的時候才會把前面的操作寫入binlog資訊,所以對於binlog來說, 分散式交易與普通的事務沒有區別 ,而prepare以前的操作資訊都儲存在串連的IO_CACHE中,如果這個時候用戶端退出了,以前的binlog資訊都會被丟失,再次重連後允許提交的話,會造成Binlog丟失,從而造成主從資料的不一致,所以官方在用戶端退出的時候直接把已經prepare的事務都復原了!

官方的做法,貌似幹得很漂亮,犧牲了一點標準化的東西,至少保證了主從資料的一致性。但其實不然,若使用者已經prepare後在用戶端退出之前,MySQL發生了宕機,這個時候又會怎樣?

MySQL在某個分散式交易prepare成功後宕機,宕機前操作該事務的串連並沒有斷開,這個時候已經prepare的事務並不會被復原,所以在MySQL重新啟動後,引擎層通過recover機制能恢複該事務。當然該事務的Binlog已經在宕機過程中被丟失,這個時候,如果去提交,則會造成主從資料的不一致, 即提交沒有記錄Binlog,從上丟失該條資料。 所以對於這種情況,官方一般建議直接復原已經prepare的事務。

以上是MySQL-5.7以前版本MySQL在分散式交易上的各種問題,那麼MySQL-5.7版本官方做了哪些改進?這個可以從官方的 WL#6860 描述上得到一些資訊,我們還是本著沒有實踐就沒有發言權的態度,從具體的操作上來分析下MySQL-5.7的改進方法:

還是以上面同樣的表結構進行同樣的操作如下:

mysql> XA START 'mysql57';
mysql> INSERT INTO t VALUES(1,1);
mysql> XA END 'mysql57';
mysql> XA PREPARE 'mysql57'

這個時候,我們通過mysqlbinlog來查看下Master上的Binlog,結果如下:

同時也對比下Slave上的Relay log,如下:

通過上面的操作,明顯發現在prepare以後,從XA START到XA PREPARE之間的操作都被記錄到了Master的Binlog中,然後通過複製關係傳到了Slave上。也就是說MySQL-5.7開始,MySQL對於分散式交易,在prepare的時候就完成了寫Binlog的操作,通過新增一種叫 XA_prepare_log_event 的event類型來實現,這是與以前版本的主要區別(以前版本prepare時不寫Binlog)

當然僅靠這一點是不夠的,因為我們知道Slave通過SQL thread來回放Relay log資訊,由於prepare的事務能阻塞整個session,而回放的SQL thread只有一個(不考慮並行回放),那麼SQL thread會不會因為被分散式交易的prepare階段所阻塞,從而造成整個SQL thread回放出現問題?這也正是官方要解決的第二個問題:怎麼樣能使SQL thread在回放到分散式交易的prepare階段時,不阻塞後面event的回放?其實這個實現也很簡單(在xa.cc::applier_reset_xa_trans),只要在SQL thread回放到prepare的時候,進行類似於用戶端中斷連線的處理即可(把相關cache與SQL thread的串連控制代碼脫離)。最後在Slave伺服器上,使用者通過命令XA RECOVER可以查到如下資訊:

mysql> XA RECOVER;
+----------+--------------+--------------+---------+
| formatID | gtrid_length | bqual_length | data    |
+----------+--------------+--------------+---------+
| 1        | 7            | 0            | mysql57 |
+----------+--------------+--------------+---------+

至於上面的事務什麼時候提交,一般等到Master上進行XA COMMIT  ‘mysql57’後,slave上也同時會被提交。

總結

綜上所述,MySQL 5.7對於分散式交易的支援變得完美了,一個長達數十年的bug又被修複了,因而又多了一個升級到MySQL-5.7版本的理由。

本文永久更新連結地址:

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.