全網最牛X的!!! MySQL兩階段提交串講

賜我白日夢發表於2021-01-10


一、吹個牛

面試官的一句:“瞭解MySQL的兩階段提交嗎?” 不知道問涼了多少人!

這篇文章白日夢就和大家分享什麼是MySQL的兩階提交到底是怎麼回事!不管你原來曉不曉得兩階段提交,相信我!這篇文章中你一定能get到新的知識!

在說兩階段提交之前,白日夢用了大量的篇幅再講undo-log、redo-log、binlog。

先了解它們,才能更好的理解什麼是兩階段提交,如果你如果還沒有看,推薦你去翻一翻前面的文章。


二、事務及它的特性

在說兩階段提交事物之前,我們先來說說事務。

一般當我們的功能函式中有批量的增刪改時,我們會新增一個事物包裹這一系列的操作,要麼這一組操作全部執行成功,只要有一條SQL執行失敗了我們就全部回滾。相信你一定聽說過這個比較經典的轉賬的Case。有一定工作經驗的同學都知道,這麼做其實是保護我們的資料庫中不出現髒資料。整體資料會變的可控。

對MySQL來說你可以通過下面的命令顯示的開啟、提交、回滾事務

# 開啟事務
begin;

# 或者下面這條命令
start transaction;

# 提交
commit;

# 回滾
rollback;

但是日常開發中大家普遍使用程式語言運算元據庫。比如Java、Golang... 在使用這種具體程式語言持久層的框架時,它們一般都支援事務操作,比如:在Spring中你可以對一個方法新增註解@Transctional顯示的開啟事務。Golang的beego中也提供了讓你可以顯示的開啟事務的函式。

有一點不太好的地方是:大家在享受這種程式設計框架帶來的便利的同時,它也遮蔽了你對MySQL事務認知。讓人們懶得去往細了看事務

你可以往看我下面這個很簡單的Case。

我有一張資料表

CREATE TABLE `test_backup` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然後我往這個表中insert幾條資料

mysql> insert into test_backup values(1,'tom');
mysql> insert into test_backup values(2,'jerry');
mysql> insert into test_backup values(1,'herry');

再去檢視binlog。

你會不會詫異?我上面明明沒有顯示的新增begin、commit命令,但是MySQL實際執行我的SQL時,竟然為我新增上了!

原因很簡單:跟大家分享一個引數如下:

一般大家的線上庫都會將這個引數置為ON,你的SQL會自動的開啟一個事物,並且MySQL會自動的幫你把它提交。

也就是說: 當這個引數為ON時,你使用的DAO持久層框架傳送給資料庫的SQL其實都會被放在一個事物中執行,然後這個事物被自動提交,而我們對這個過程是無感知的。 具體一點,比如你使用某框架的@Transctional註解,或者在golang中可以像下面的方式獲得一個事物:

db := mysql.Client
ops := &sql.TxOptions{
		Isolation: 0,
		ReadOnly:  false,
	}
tx, err := db.BeginTx(ctx, ops)
// todo with tx

然後你所有的操作都放在這個事物中執行。

這時你使用的持久層框架肯定會向MySQL傳送一條命令:`begin;`或者是`start transcation;`來保證你這一組SQL中執行一條SQL後,開啟的事物不會被MySQL自動幫你提交了。

其實還是推薦將這個引數設定成ON的,當然你也可以像下面這樣將它關閉

mysql> set autocommit = 0;

但是關閉它之後,MySQL不會幫你自動提交事物,全靠研發同學自己來維護就容易會出現長事物,在記憶體中產生一個極其長的undo log鏈條。壞處多多。

todo 關於長事物,你可以看白日夢的這篇筆記:


三、簡單看下兩階段提交的流程

瞭解了什麼是事物,再來看下什麼是兩階段提交。其實所謂的兩階段就是把一個事物分成兩個階段來提交。就像下圖這樣。

上圖為兩階段提交的時序圖。

你可以粗略的觀察一下上圖,MySQL想要準備事務的時候會先寫redolog、binlog分成兩個階段。

兩階段提交的第一階段 (prepare階段):寫rodo-log 並將其標記為prepare狀態。

緊接著寫binlog

兩階段提交的第二階段(commit階段):寫bin-log 並將其標記為commit狀態。

不瞭解這些日誌是什麼有啥用也沒關係,你可以先去看我之前的系列文章。


四、兩階段寫日誌用意?

你有沒有想過這樣一件事,binlog預設都是不開啟的狀態!

也就是說,如果你根本不需要binlog帶給你的特性(比如資料備份恢復、搭建MySQL主從叢集),那你根本就用不著讓MySQL寫binlog,也用不著什麼兩階段提交。

只用一個redolog就夠了。無論你的資料庫如何crash,redolog中記錄的內容總能讓你MySQL記憶體中的資料恢復成crash之前的狀態。

所以說,兩階段提交的主要用意是:為了保證redolog和binlog資料的安全一致性。只有在這兩個日誌檔案邏輯上高度一致了。你才能放心的使用redolog幫你將資料庫中的狀態恢復成crash之前的狀態,使用binlog實現資料備份、恢復、以及主從複製。而兩階段提交的機制可以保證這兩個日誌檔案的邏輯是高度一致的。沒有錯誤、沒有衝突。

當然,兩階段提交能做到足夠的安全還需要你合理的設定redolog和binlog的fsync的時機,而這塊知識點所涉及到的引數前幾篇文章已經說過。如果不記得,可以去看下。


五、加餐:sync_binlog = 1 問題

如果你看懂了我下面說的這些話,能幫你更好的理解兩階段提交哦!純乾貨!

白日夢在前面的分享binlog的文章中有跟大家提到過一個引數sync_binlog=1。這個引數控制binlog的落盤時機,並且白日夢也知道你們公司線上資料庫的該引數一定被設定成了1。

我在那篇binlog文章之前,就計劃好寫這篇文章了。白日夢的MySQL在動筆之前已經列好了大綱,從簡單到複雜,從0到1開始更新,歡迎小夥伴們關注我,持續更新中~

Notice!!! 這個引數為1時,表示當事物提交時會將binlog落盤。

現在你用15s中的時間,思考一下,藍色句子中說的事物提交時會將binlog落盤,這個提交時,是下圖中的step1時刻呢?還是step2時刻呢?

答案是:step1時刻!

知道這個知識點很重要,下面我來描述這樣一個場景。

假如要執行一條update語句,那你肯定知道,先寫redolog(便於後續對update事務的回滾)。然後你的update邏輯將Buffer Pool中的快取頁修改成了髒頁。

當你準備提交事物時(也就是step1階段),會寫redolog,並將其標記為prepare階段。然後再寫binlog,並將binlog落盤。

然後發生了意外,MySQL當機了。

那我問你,當你重啟MySQL後,update對BufferPool中做出的修改是會被回滾還是會被提交呢?

答案是:會根據redolog將修改後的recovery出來,然後提交。

那為什麼會這樣做呢?

其實總的來說,不論mysql什麼時刻crash,最終是commit還是rollback完全取決於MySQL能不能判斷出binlog和redolog在邏輯上是否達成了一致。只要邏輯上達成了一致就可以commit,否則只能rollback。

比如還是上面描述的場景,binlog已經寫了,但是MySQL最終選擇了回滾。那代表你的binlog比BufferPool(或者Disk)中的真實資料多出一條更新,日後你用這份binlog做資料恢復,是不是結果一定是錯誤的?


六、如何判斷binlog和redolog是否達成了一致

這個知識點可是純乾貨!

當MySQL寫完redolog並將它標記為prepare狀態時,並且會在redolog中記錄一個XID,它全域性唯一的標識著這個事務。而當你設定`sync_binlog=1`時,做完了上面第一階段寫redolog後,mysql就會對應binlog並且會直接將其重新整理到磁碟中。

下圖就是磁碟上的row格式的binlog記錄。binlog結束的位置上也有一個XID。

只要這個XID和redolog中記錄的XID是一致的,MySQL就會認為binlog和redolog邏輯上一致。就上面的場景來說就會commit,而如果僅僅是rodolog中記錄了XID,binlog中沒有,MySQL就會RollBack


七、兩階段提交設計的初衷 - 分散式事務

其實兩階段提交更多的被使用在分散式事務的場景。

我用大白話描述一個這樣的場景,大家自行腦補一下:

MySQL單機本來是支援事務的,但是這裡所謂的分散式事務實際上指的是跨資料庫、跨叢集的事務。比如說你公司的業務太火爆了,每天都產生大量的資料,這些資料不僅單表存不下,甚至單庫都存不下了(已經達到了伺服器硬體儲存的瓶頸)

那你怎麼辦?是不是隻能將單庫拆分成多庫?

那你拆分成多庫就會面臨這樣一個新的問題。假設Tom給Jerry轉賬,但是由於你拆分了資料庫,原本在同庫同表上的Tom和Jerry的資訊,被你拆分進A庫a表和B庫b表。那你再發起轉賬邏輯時,萬一失敗了。如何回滾保證資料的安全?這就是分散式事務的要解決的問題。

通常各大公司都有自己的支援分散式事務中介軟體,中介軟體的作用本質上就是處理好各個資料庫節點之間兩階段提交的問題。

簡單來說:就是中介軟體要協調各個資料節點。

第一階段:中介軟體告訴各資料庫節點,讓它們開啟XA事務,然後判斷所有資料庫節點是否已經處於prepare狀態

第二階段:中介軟體判斷事務提交還是回滾的階段。如果所有節點都prepare那就統一提交。但凡出現一個失敗的節點,統一回滾。

這裡只是稍微提及一下:兩階段提交和分散式事務的淵源。

白日夢後續計劃還會有文章中進一步跟大家詳細的分享分散式事務話題。


八、再看MySQL兩階段寫日誌

那我們再將思路拉回到MySQL兩階段寫日誌的話題。

其實說到這裡,你大概也能直接想到,其實上一篇文章中的兩階段提交,表面上其實就是兩階段寫入日誌。

通過我前面的描述,你也一定知道了兩份日誌檔案邏輯對齊的標記是有一份相同的XID。

就是這種兩階段的機制保證了兩個日誌(在分散式事務中就是多個資料節點)在邏輯上能達到一致的效果。


九、留一個彩蛋

如果你仔細想一下,上面第三部分在分享 sync_binlog=1 加餐時,我所描述的示例場景其實是適用於單機MySQL的簡單場景。

其實這個場景還能再複雜一些!

串聯MySQL叢集、將同步、半同步、非同步的主從複製關係以及這裡的兩階段提交、日誌的落盤時機、幽靈事務!結合成一個場景效果會更好。

但是我將它放在《為研發同學定製的面試指南》排期的後半部分也就是MySQL叢集部分。讓我們從易到難過度過去! 歡迎關注白日夢。


十、推薦閱讀(公眾號首發,歡迎關注白日夢)

  1. MySQL的修仙之路,圖文談談如何學MySQL、如何進階!(已釋出)
  2. 面前突擊!33道資料庫高頻面試題,你值得擁有!(已釋出)
  3. 大家常說的基數是什麼?(已釋出)
  4. 講講什麼是慢查!如何監控?如何排查?(已釋出)
  5. 對NotNull欄位插入Null值有啥現象?(已釋出)
  6. 能談談 date、datetime、time、timestamp、year的區別嗎?(已釋出)
  7. 瞭解資料庫的查詢快取和BufferPool嗎?談談看!(已釋出)
  8. 你知道資料庫緩衝池中的LRU-List嗎?(已釋出)
  9. 談談資料庫緩衝池中的Free-List?(已釋出)
  10. 談談資料庫緩衝池中的Flush-List?(已釋出)
  11. 瞭解髒頁刷回磁碟的時機嗎?(已釋出)
  12. 用十一張圖講清楚,當你CRUD時BufferPool中發生了什麼!以及BufferPool的優化!(已釋出)
  13. 聽說過表空間沒?什麼是表空間?什麼是資料表?(已釋出)
  14. 談談MySQL的:資料區、資料段、資料頁、資料頁究竟長什麼樣?瞭解資料頁分裂嗎?談談看!(已釋出)
  15. 談談MySQL的行記錄是什麼?長啥樣?(已釋出)
  16. 瞭解MySQL的行溢位機制嗎?(已釋出)
  17. 說說fsync這個系統呼叫吧! (已釋出)
  18. 簡述undo log、truncate、以及undo log如何幫你回滾事物! (已釋出)
  19. 我勸!這位年輕人不講MVCC,耗子尾汁! (已釋出)
  20. MySQL的崩潰恢復到底是怎麼回事? (已釋出)
  21. MySQL的binlog有啥用?誰寫的?在哪裡?怎麼配置 (已釋出)
  22. MySQL的bin log的寫入機制 (已釋出)
  23. 刪庫後!除了跑路還能幹什麼?(已釋出)

相關文章