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

我們看到在MySQL 5。7版本里大量遺留很多年的bug都被fix掉了,bug#12161就是其中一個,該bug在2005年第一次report到Bug list上,十年之後終於在MySQL 5。7。7 第一個RC版本被fix了。

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

最近整理的Java架構學習影片和大廠專案底層知識點,需要的同學歡迎私信我【資料】發給你~一起學習進步!

Bug描述

當我們顯式開啟一個XA事務,執行操作,並完成XA PREPARE後,如果Kill session或者主動斷開再重連執行XA RECOVER,之前的這個XA事務就會直接丟失掉了。

例如:

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恢復的:

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丟失,導致主備資料不一致。

bug產生的原因也很簡單:在退出session時,執行緒總是會去無條件的回滾掉自己尚未提交的事務。

官方修復

持久化

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

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

Query event : XA START X‘616263’,X‘’,1 // 這裡的’616262‘即是’abc‘的十六進位制編碼Table_map eventWrite_rows eventQuery event:XA END X’616263‘,X’‘,1XA_prepare event: XA PREPARE X’616263‘,X’’,1

這時候該XA事務同時在InnoDB層(事務處於Prepare狀態,Redo持久化到磁碟)和Server層都有持久化資訊。

其中XA_PREPARE事件是新引入的事件型別(內部類為XA_prepare_event),以後版本升級需要注意到這個低版本不相容事件。

然後再執行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架構學習影片和大廠專案底層知識點,需要的同學歡迎私信我發給你~一起學習進步!有任何問題也歡迎交流~

Java日記本,每日存檔超實用的技術乾貨學習筆記,每天陪你前進一點點~