修復MySQL中人人頭疼的Bug,看這篇就夠!

咱們看到在MySQL 5.7版本里大量遺留不少年的bug都被fix掉了,bug#12161就是其中一個,該bug在2005年第一次report到Bug list上,十年以後終於在MySQL 5.7.7 第一個RC版本被fix了。mysql

Bug描述

當咱們顯式開啓一個XA事務,執行操做,並完成XA PREPARE後,若是Kill session或者主動斷開再重連執行XA RECOVER,以前的這個XA事務就會直接丟失掉了。git

例如:sql

mysql> XA BEGIN 'abc';
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO t1 VALUES (1,2,3);
Query OK, 1 row affected (0.00 sec)

mysql> XA END 'abc';
Query OK, 0 rows affected (0.00 sec)

mysql> XA PREPARE 'abc';
Query OK, 0 rows affected (0.00 sec)

mysql> Ctrl-C -- exit!
Aborted

mysql> XA RECOVER;
Empty set (0.00 sec)
複製代碼

有趣的是,若是在XA PREPARE後把實例KILL掉,是能夠經過XA RECOVER恢復的:markdown

mysql> XA RECOVER;
+----------+--------------+--------------+------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------+
| 1 | 3 | 0 | abc |
+----------+--------------+--------------+------+
1 row in set (0.00 sec)

mysql> XA COMMIT 'abc';
Query OK, 0 rows affected (0.00 sec)
複製代碼

雖然實例異常重啓能夠恢復事務,但引入的另一個問題是:事務變動的binlog丟失,致使主備數據不一致。session

bug產生的緣由也很簡單:在退出session時,線程老是會去無條件的回滾掉本身還沒有提交的事務。函數

官方修復

持久化

爲了解決這個問題,將XA的兩階段記錄到了Binlog中;編碼

對於上文描述的序列,當執行到XA PREPARE時,記錄第一階段的binlog,以下:spa

Query event : XA START X'616263',X'’,1  // 這裏的'616262'便是'abc'的十六進制編碼
Table_map event
Write_rows event
Query event:XA END X'616263',X'',1
XA_prepare event: XA PREPARE X'616263',X'’,1
複製代碼

這時候該XA事務同時在InnoDB層(事務處於Prepare狀態,Redo持久化到磁盤)和Server層都有持久化信息。線程

其中XA_PREPARE事件是新引入的事件類型(內部類爲XA_prepare_event),之後版本升級須要注意到這個低版本不兼容事件。rest

而後再執行XA COMMIT ‘abc’,產生新的事件:

Query event:XA COMMIT X'616263',X'',1
複製代碼

若是執行XA ROLLBACK,則記錄:

Query event:XA ROLLBACK X'616263',X'',1
複製代碼

因爲XA PREPARE和XA COMMIT是分開執行的,所以在這兩個事件中間可能存在別的事務,備庫複製線程須要處理這種狀況。

爲了實現XA PREPARE寫binlog,對binlog_prepare進行了擴展,這裏會調用mysql_bin_log.commit, 將cache中的binlog刷到文件中。

Tips:XID能夠包含三個部分:gtrid, [, bqual [, format ID]],其中gtrid是必選的,表示全局標識,bqual是分支標識,默認爲空’‘,format ID是一個unsigned整型,默認值爲1,在上例中,咱們只指定了gtrid爲’abc’,所以bqual段和format ID均爲默認值。更具體的描述參考官方文檔。

如何恢復

當會話斷開時(例如kill session或者一次乾淨的shutdown/restart操做),咱們必需要能恢復該事務,以前的邏輯是在cleanup時,直接回滾全部的活躍事務。在新版本中,對XA PREPARE的事務作了特殊處理(THD::cleanup),若是處於Prepare狀態,就將事務的in_recovery設置爲TRUE,並更新到hash表transaction_cache中(transaction_cache_detach),該hash表用於維護全部XA事務。

對於非XA的活躍事務,在會話斷開時,依然採用回滾策略。

當重連客戶端後,咱們能夠直接執行 XA COMMIT ‘abc’,這時候會經過XID關鍵字去搜索transaction_cache並將對應的事務提交掉。

同時BINLOG的狀態要保持一致,若是會話斷開前的XA PREPARE沒有記錄Binlog, 重連後執行XA COMMIT也不該該記錄。

備庫複製

因爲XA PREPARE和XA COMMIT是分開記錄的,當碰到XA COMMIT時,備庫採用等待以前的事務所有完成,而後再執行的方式(至關於退化到串行)。

另外,咱們知道在一個正常的會話過程當中,老是爲其cache一個事務對象,新的事務會重用這個事務對象,避免屢次分配;而XA事務的COMMIT和PREPARE是分離的,須要爲XA事務單獨分配事務對象。所以複製線程執行XA START時,將其擁有的事務對象臨時保存起來(detach_native_trx),當執行到XA_prepare_log_event事件時,再將其恢復給複製線程,同時XA事務對象關閉read view,將is_recovered設置爲TRUE(函數innodb_replace_trx_in_thd)。

隨後複製線程在執行到XA COMMIT時直接根據XID找到對應的XA事務進行提交。

參考:

WL#6860 Binlogging XA-prepared transaction Github:git show f4c37f7aea732763947980600c6882ec908a54a0 MySQL 5.7.7-RC

有任何問題歡迎留言交流~

看到這裏的小夥伴,若是你喜歡這篇文章的話,別忘了轉發、收藏、留言互動

若是對文章有任何問題,歡迎在留言區和我交流~

最近我新整理了一些Java資料,包含面經分享、模擬試題、和視頻乾貨,若是你須要的話,歡迎留言or私信我

還有,關注我!關注我!關注我!

相關文章
相關標籤/搜索