MySQL兩階段提交串講

目錄:

  • 一、吹個(gè)牛
  • 二、事務(wù)及它的特性
  • 三、簡單看下兩階段提交的流程
  • 四、兩階段寫日志用意?
  • 五、加餐:sync_binlog = 1 問題
  • 六、如何判斷binlog和redolog是否一致
  • 七、兩階段提交設(shè)計(jì)的初衷 - 分布式事務(wù)
  • 八、再看MySQL兩階段寫日志
  • 九、留一個(gè)彩蛋
  • 十、推薦閱讀

一、吹個(gè)牛

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

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

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

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

二、事務(wù)及它的特性

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

一般當(dāng)我們的功能函數(shù)中有批量的增刪改時(shí),我們會添加一個(gè)事物包裹這一系列的操作,要么這一組操作全部執(zhí)行成功,只要有一條SQL執(zhí)行失敗了我們就全部回滾。相信你一定聽說過這個(gè)比較經(jīng)典的轉(zhuǎn)賬的Case。有一定工作經(jīng)驗(yàn)的同學(xué)都知道,這么做其實(shí)是保護(hù)我們的數(shù)據(jù)庫中不出現(xiàn)臟數(shù)據(jù)。整體數(shù)據(jù)會變的可控。

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

# 開啟事務(wù)
begin;

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

# 提交
commit;

# 回滾
rollback;
復(fù)制代碼

但是日常開發(fā)中大家普遍使用編程語言操作數(shù)據(jù)庫。比如Java、Golang... 在使用這種具體編程語言持久層的框架時(shí),它們一般都支持事務(wù)操作,比如:在Spring中你可以對一個(gè)方法添加注解@Transctional顯示的開啟事務(wù)。Golang的beego中也提供了讓你可以顯示的開啟事務(wù)的函數(shù)。

有一點(diǎn)不太好的地方是:大家在享受這種編程框架帶來的便利的同時(shí),它也屏蔽了你對MySQL事務(wù)認(rèn)知。讓人們懶得去往細(xì)了看事務(wù)

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

我有一張數(shù)據(jù)表

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

然后我往這個(gè)表中insert幾條數(shù)據(jù)

mysql> insert into test_backup values(1,'tom');
mysql> insert into test_backup values(2,'jerry');
mysql> insert into test_backup values(1,'herry');
復(fù)制代碼

再去查看binlog。


你會不會詫異?我上面明明沒有顯示的添加begin、commit命令,但是MySQL實(shí)際執(zhí)行我的SQL時(shí),竟然為我添加上了!

原因很簡單:跟大家分享一個(gè)參數(shù)如下:

一般大家的線上庫都會將這個(gè)參數(shù)置為ON,你的SQL會自動的開啟一個(gè)事物,并且MySQL會自動的幫你把它提交。

也就是說: 當(dāng)這個(gè)參數(shù)為ON時(shí),你使用的DAO持久層框架發(fā)送給數(shù)據(jù)庫的SQL其實(shí)都會被放在一個(gè)事物中執(zhí)行,然后這個(gè)事物被自動提交,而我們對這個(gè)過程是無感知的。具體一點(diǎn),比如你使用某框架的@Transctional注解,或者在golang中可以像下面的方式獲得一個(gè)事物:

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

然后你所有的操作都放在這個(gè)事物中執(zhí)行。

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

其實(shí)還是推薦將這個(gè)參數(shù)設(shè)置成ON的,當(dāng)然你也可以像下面這樣將它關(guān)閉

mysql> set autocommit = 0;
復(fù)制代碼

但是關(guān)閉它之后,MySQL不會幫你自動提交事物,全靠研發(fā)同學(xué)自己來維護(hù)就容易會出現(xiàn)長事物,在內(nèi)存中產(chǎn)生一個(gè)極其長的undo log鏈條。壞處多多。

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

了解了什么是事物,再來看下什么是兩階段提交。其實(shí)所謂的兩階段就是把一個(gè)事物分成兩個(gè)階段來提交。就像下圖這樣。

上圖為兩階段提交的時(shí)序圖。

你可以粗略的觀察一下上圖,MySQL想要準(zhǔn)備事務(wù)的時(shí)候會先寫redolog、binlog分成兩個(gè)階段。

兩階段提交的第一階段 (prepare階段):寫rodo-log 并將其標(biāo)記為prepare狀態(tài)。

緊接著寫binlog

兩階段提交的第二階段(commit階段):寫bin-log 并將其標(biāo)記為commit狀態(tài)。

不了解這些日志是什么有啥用也沒關(guān)系,你可以先去看我之前的系列文章。

四、兩階段寫日志用意?

你有沒有想過這樣一件事,binlog默認(rèn)都是不開啟的狀態(tài)!

也就是說,如果你根本不需要binlog帶給你的特性(比如數(shù)據(jù)備份恢復(fù)、搭建MySQL主從集群),那你根本就用不著讓MySQL寫binlog,也用不著什么兩階段提交。

只用一個(gè)redolog就夠了。無論你的數(shù)據(jù)庫如何crash,redolog中記錄的內(nèi)容總能讓你MySQL內(nèi)存中的數(shù)據(jù)恢復(fù)成crash之前的狀態(tài)。

所以說,兩階段提交的主要用意是:為了保證redolog和binlog數(shù)據(jù)的安全一致性。只有在這兩個(gè)日志文件邏輯上高度一致了。你才能放心的使用redolog幫你將數(shù)據(jù)庫中的狀態(tài)恢復(fù)成crash之前的狀態(tài),使用binlog實(shí)現(xiàn)數(shù)據(jù)備份、恢復(fù)、以及主從復(fù)制。而兩階段提交的機(jī)制可以保證這兩個(gè)日志文件的邏輯是高度一致的。沒有錯(cuò)誤、沒有沖突。

當(dāng)然,兩階段提交能做到足夠的安全還需要你合理的設(shè)置redolog和binlog的fsync的時(shí)機(jī),而這塊知識點(diǎn)所涉及到的參數(shù)前幾篇文章已經(jīng)說過。如果不記得,可以去看下。

五、加餐:sync_binlog = 1 問題

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

白日夢在前面的分享binlog的文章中有跟大家提到過一個(gè)參數(shù)sync_binlog=1。這個(gè)參數(shù)控制binlog的落盤時(shí)機(jī),并且白日夢也知道你們公司線上數(shù)據(jù)庫的該參數(shù)一定被設(shè)置成了1。

我在寫那篇binlog文章之前,就計(jì)劃好寫這篇文章了。白日夢的MySQL在動筆之前已經(jīng)列好了大綱,從簡單到復(fù)雜,從0到1開始更新,歡迎小伙伴們關(guān)注我,持續(xù)更新中~

Notice?。?!這個(gè)參數(shù)為1時(shí),表示當(dāng)事物提交時(shí)會將binlog落盤。

現(xiàn)在你用15s中的時(shí)間,思考一下,藍(lán)色句子中說的事物提交時(shí)會將binlog落盤,這個(gè)提交時(shí),是下圖中的step1時(shí)刻呢?還是step2時(shí)刻呢?

答案是:step1時(shí)刻!

知道這個(gè)知識點(diǎn)很重要,下面我來描述這樣一個(gè)場景。

假如要執(zhí)行一條update語句,那你肯定知道,先寫undolog(便于后續(xù)對update事務(wù)的回滾)。然后你的update邏輯將Buffer Pool中的緩存頁修改成了臟頁。

當(dāng)你準(zhǔn)備提交事物時(shí)(也就是step1階段),會寫redolog,并將其標(biāo)記為prepare階段。然后再寫binlog,并將binlog落盤。

這時(shí)發(fā)生了意外,MySQL宕機(jī)了。

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

答案是:會根據(jù)redolog將修改后的recovey出來,然后提交。

那為什么會這樣做呢?

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

比如還是上面描述的場景,binlog已經(jīng)寫了,如果MySQL最終選擇了回滾。那代表你的binlog比BufferPool(或者Disk)中的真實(shí)數(shù)據(jù)多出一條更新,日后你用這份binlog做數(shù)據(jù)恢復(fù),是不是結(jié)果一定是錯(cuò)誤的?

或者說binlog已經(jīng)落盤了,那很有可能所有的從庫都把這條binlog拿走自己回放了,這時(shí)如果主庫選擇回滾丟棄這條數(shù)據(jù),那是不是主從數(shù)據(jù)不一致了?\

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

這個(gè)知識點(diǎn)可是純干貨!

當(dāng)MySQL寫完redolog并將它標(biāo)記為prepare狀態(tài)時(shí),并且會在redolog中記錄一個(gè)XID,它全局唯一的標(biāo)識著這個(gè)事務(wù)。而當(dāng)你設(shè)置sync_binlog=1時(shí),做完了上面第一階段寫redolog后,mysql就會對應(yīng)binlog并且會直接將其刷新到磁盤中。

下圖就是磁盤上的row格式的binlog記錄。binlog結(jié)束的位置上也有一個(gè)XID。

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

七、兩階段提交設(shè)計(jì)的初衷 - 分布式事務(wù)

其實(shí)兩階段提交更多的被使用在分布式事務(wù)的場景。

我用大白話描述一個(gè)這樣的場景,大家自行腦補(bǔ)一下:

MySQL單機(jī)本來是支持事務(wù)的,但是這里所謂的分布式事務(wù)實(shí)際上指的是跨數(shù)據(jù)庫、跨集群的事務(wù)。比如說你公司的業(yè)務(wù)太火爆了,每天都產(chǎn)生大量的數(shù)據(jù),這些數(shù)據(jù)不僅單表存不下,甚至單庫都存不下了(已經(jīng)達(dá)到了服務(wù)器硬件存儲的瓶頸)

那你怎么辦?是不是只能將單庫拆分成多庫?

那你拆分成多庫就會面臨這樣一個(gè)新的問題。假設(shè)Tom給Jerry轉(zhuǎn)賬,但是由于你拆分了數(shù)據(jù)庫,原本在同庫同表上的Tom和Jerry的信息,被你拆分進(jìn)A庫a表和B庫b表。那你再發(fā)起轉(zhuǎn)賬邏輯時(shí),萬一失敗了。如何回滾保證數(shù)據(jù)的安全?這就是分布式事務(wù)的要解決的問題。

通常各大公司都有自己的支持分布式事務(wù)中間件,中間件的作用本質(zhì)上就是處理好各個(gè)數(shù)據(jù)庫節(jié)點(diǎn)之間兩階段提交的問題。

簡單來說:就是中間件要協(xié)調(diào)各個(gè)數(shù)據(jù)節(jié)點(diǎn)。

第一階段:中間件告訴各數(shù)據(jù)庫節(jié)點(diǎn),讓它們開啟XA事務(wù),然后判斷所有數(shù)據(jù)庫節(jié)點(diǎn)是否已經(jīng)處于prepare狀態(tài)

第二階段:中間件判斷事務(wù)提交還是回滾的階段。如果所有節(jié)點(diǎn)都prepare那就統(tǒng)一提交。但凡出現(xiàn)一個(gè)失敗的節(jié)點(diǎn),統(tǒng)一回滾。

這里只是稍微提及一下:兩階段提交和分布式事務(wù)的淵源。

白日夢后續(xù)計(jì)劃還會有文章中進(jìn)一步跟大家詳細(xì)的分享分布式事務(wù)話題。

八、再看MySQL兩階段寫日志

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

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

通過我前面的描述,你也一定知道了兩份日志文件邏輯對齊的標(biāo)記是有一份相同的XID。

就是這種兩階段的機(jī)制保證了兩個(gè)日志(在分布式事務(wù)中就是多個(gè)數(shù)據(jù)節(jié)點(diǎn))在邏輯上能達(dá)到一致的效果。

九、留一個(gè)彩蛋

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

其實(shí)這個(gè)場景還能再復(fù)雜一些!

串聯(lián)MySQL集群、將同步、半同步、異步的主從復(fù)制關(guān)系以及這里的兩階段提交、日志的落盤時(shí)機(jī)、幽靈事務(wù)!結(jié)合成一個(gè)場景效果會更好。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容