[TOC]
一、吹個(gè)牛
面試官的一句:“了解MySQL的兩階段提交嗎里伯?” 不知道問涼了多少人城瞎!
這篇文章白日夢(mèng)就和大家分享什么是MySQL的兩階提交到底是怎么回事!不管你原來曉不曉得兩階段提交疾瓮,相信我脖镀!這篇文章中你一定能get到新的知識(shí)!
在說兩階段提交之前狼电,白日夢(mèng)用了大量的篇幅再講undo-log蜒灰、redo-log弦蹂、binlog。
先了解它們强窖,才能更好的理解什么是兩階段提交凸椿,如果你如果還沒有看,推薦你去翻一翻前面的文章翅溺。
二脑漫、事務(wù)及它的特性
在說兩階段提交事物之前,我們先來說說事務(wù)咙崎。
一般當(dāng)我們的功能函數(shù)中有批量的增刪改時(shí)优幸,我們會(huì)添加一個(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ù)會(huì)變的可控。
對(duì)MySQL來說你可以通過下面的命令顯示的開啟新啼、提交追城、回滾事務(wù)
# 開啟事務(wù)
begin;
# 或者下面這條命令
start transaction;
# 提交
commit;
# 回滾
rollback;
但是日常開發(fā)中大家普遍使用編程語言操作數(shù)據(jù)庫。比如Java燥撞、Golang... 在使用這種具體編程語言持久層的框架時(shí)座柱,它們一般都支持事務(wù)操作,比如:在Spring中你可以對(duì)一個(gè)方法添加注解@Transctional
顯示的開啟事務(wù)物舒。Golang的beego中也提供了讓你可以顯示的開啟事務(wù)的函數(shù)色洞。
<p style="color:red">有一點(diǎn)不太好的地方是:大家在享受這種編程框架帶來的便利的同時(shí),它也屏蔽了你對(duì)MySQL事務(wù)認(rèn)知冠胯。讓人們懶得去往細(xì)了看事務(wù)
<p style="color:blue">你可以往看我下面這個(gè)很簡(jiǎn)單的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;
然后我往這個(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');
再去查看binlog。
<p style="color:green">你會(huì)不會(huì)詫異荠察?我上面明明沒有顯示的添加begin置蜀、commit命令,但是MySQL實(shí)際執(zhí)行我的SQL時(shí)悉盆,竟然為我添加上了盯荤!
原因很簡(jiǎn)單:跟大家分享一個(gè)參數(shù)如下:
一般大家的線上庫都會(huì)將這個(gè)參數(shù)置為ON,你的SQL會(huì)自動(dòng)的開啟一個(gè)事物焕盟,并且MySQL會(huì)自動(dòng)的幫你把它提交秋秤。
也就是說: 當(dāng)這個(gè)參數(shù)為ON時(shí),你使用的DAO持久層框架發(fā)送給數(shù)據(jù)庫的SQL其實(shí)都會(huì)被放在一個(gè)事物中執(zhí)行,然后這個(gè)事物被自動(dòng)提交灼卢,而我們對(duì)這個(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
然后你所有的操作都放在這個(gè)事物中執(zhí)行蛇摸。
<p style="color:red">這時(shí)你使用的持久層框架肯定會(huì)向MySQL發(fā)送一條命令:begin;
或者是start transcation;
來保證你這一組SQL中執(zhí)行一條SQL后,開啟的事物不會(huì)被MySQL自動(dòng)幫你提交了灿巧。
其實(shí)還是推薦將這個(gè)參數(shù)設(shè)置成ON
的,當(dāng)然你也可以像下面這樣將它關(guān)閉
mysql> set autocommit = 0;
但是關(guān)閉它之后揽涮,MySQL不會(huì)幫你自動(dòng)提交事物抠藕,全靠研發(fā)同學(xué)自己來維護(hù)就容易會(huì)出現(xiàn)長(zhǎng)事物,在內(nèi)存中產(chǎn)生一個(gè)極其長(zhǎng)的undo log鏈條蒋困。壞處多多盾似。
todo 關(guān)于長(zhǎng)事物,你可以看白日夢(mèng)的這篇筆記:
三雪标、簡(jiǎn)單看下兩階段提交的流程
了解了什么是事物零院,再來看下什么是兩階段提交。其實(shí)所謂的兩階段就是把一個(gè)事物分成兩個(gè)階段來提交村刨。就像下圖這樣告抄。
上圖為兩階段提交的時(shí)序圖。
你可以粗略的觀察一下上圖嵌牺,MySQL想要準(zhǔn)備事務(wù)的時(shí)候會(huì)先寫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)。
<p style="color:blue">所以說设哗,兩階段提交的主要用意是:為了保證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ī),而這塊知識(shí)點(diǎn)所涉及到的參數(shù)前幾篇文章已經(jīng)說過手趣。如果不記得晌该,可以去看下。
五绿渣、加餐:sync_binlog = 1 問題
<p style="color:red">如果你看懂了我下面說的這些話朝群,能幫你更好的理解兩階段提交哦!純干貨中符!
白日夢(mèng)在前面的分享binlog的文章中有跟大家提到過一個(gè)參數(shù)sync_binlog=1
姜胖。這個(gè)參數(shù)控制binlog的落盤時(shí)機(jī),并且白日夢(mèng)也知道你們公司線上數(shù)據(jù)庫的該參數(shù)一定被設(shè)置成了1舟茶。
我在那篇binlog文章之前谭期,就計(jì)劃好寫這篇文章了。白日夢(mèng)的MySQL在動(dòng)筆之前已經(jīng)列好了大綱吧凉,從簡(jiǎn)單到復(fù)雜隧出,從0到1開始更新,歡迎小伙伴們關(guān)注我阀捅,持續(xù)更新中~
<p style="color:blue">NoticeU偷伞!饲鄙! 這個(gè)參數(shù)為1時(shí)凄诞,表示當(dāng)事物提交時(shí)會(huì)將binlog落盤。
現(xiàn)在你用15s中的時(shí)間忍级,思考一下帆谍,藍(lán)色句子中說的事物提交時(shí)會(huì)將binlog落盤,這個(gè)提交時(shí)轴咱,是下圖中的step1時(shí)刻呢汛蝙?還是step2時(shí)刻呢烈涮?
<p style="color:blue">答案是:step1時(shí)刻!
知道這個(gè)知識(shí)點(diǎn)很重要窖剑,下面我來描述這樣一個(gè)場(chǎng)景坚洽。
假如要執(zhí)行一條update語句,那你肯定知道西土,先寫redolog(便于后續(xù)對(duì)update事務(wù)的回滾)讶舰。然后你的update邏輯將Buffer Pool中的緩存頁修改成了臟頁。
當(dāng)你準(zhǔn)備提交事物時(shí)(也就是step1階段)需了,會(huì)寫redolog跳昼,并將其標(biāo)記為prepare階段。然后再寫binlog肋乍,并將binlog落盤庐舟。
然后發(fā)生了意外,MySQL宕機(jī)了住拭。
<p style="color:blue">那我問你,當(dāng)你重啟MySQL后历帚,update對(duì)BufferPool中做出的修改是會(huì)被回滾還是會(huì)被提交呢滔岳?
答案是:會(huì)根據(jù)redolog將修改后的recovery出來,然后提交挽牢。
<p style="color:blue">那為什么會(huì)這樣做呢谱煤?
其實(shí)總的來說,不論mysql什么時(shí)刻crash禽拔,最終是commit還是rollback完全取決于MySQL能不能判斷出binlog和redolog在邏輯上是否達(dá)成了一致刘离。只要邏輯上達(dá)成了一致就可以commit,否則只能rollback睹栖。
比如還是上面描述的場(chǎng)景硫惕,binlog已經(jīng)寫了,但是MySQL最終選擇了回滾野来。那代表你的binlog比BufferPool(或者Disk)中的真實(shí)數(shù)據(jù)多出一條更新恼除,日后你用這份binlog做數(shù)據(jù)恢復(fù),是不是結(jié)果一定是錯(cuò)誤的曼氛?
六豁辉、如何判斷binlog和redolog是否達(dá)成了一致
<p style="color:red">這個(gè)知識(shí)點(diǎn)可是純干貨!
<p style="color:green">當(dāng)MySQL寫完redolog并將它標(biāo)記為prepare狀態(tài)時(shí)舀患,并且會(huì)在redolog中記錄一個(gè)XID徽级,它全局唯一的標(biāo)識(shí)著這個(gè)事務(wù)。而當(dāng)你設(shè)置sync_binlog=1
時(shí)聊浅,做完了上面第一階段寫redolog后餐抢,mysql就會(huì)對(duì)應(yīng)binlog并且會(huì)直接將其刷新到磁盤中现使。
<p style="color:green">下圖就是磁盤上的row格式的binlog記錄。binlog結(jié)束的位置上也有一個(gè)XID弹澎。
<p style="color:green">只要這個(gè)XID和redolog中記錄的XID是一致的朴下,MySQL就會(huì)認(rèn)為binlog和redolog邏輯上一致。就上面的場(chǎng)景來說就會(huì)commit苦蒿,而如果僅僅是rodolog中記錄了XID殴胧,binlog中沒有,MySQL就會(huì)RollBack
七佩迟、兩階段提交設(shè)計(jì)的初衷 - 分布式事務(wù)
其實(shí)兩階段提交更多的被使用在分布式事務(wù)的場(chǎng)景团滥。
我用大白話描述一個(gè)這樣的場(chǎng)景,大家自行腦補(bǔ)一下:
MySQL單機(jī)本來是支持事務(wù)的报强,但是這里所謂的分布式事務(wù)實(shí)際上指的是跨數(shù)據(jù)庫灸姊、跨集群的事務(wù)。比如說你公司的業(yè)務(wù)太火爆了秉溉,每天都產(chǎn)生大量的數(shù)據(jù)力惯,這些數(shù)據(jù)不僅單表存不下,甚至單庫都存不下了(已經(jīng)達(dá)到了服務(wù)器硬件存儲(chǔ)的瓶頸)
那你怎么辦召嘶?是不是只能將單庫拆分成多庫父晶?
那你拆分成多庫就會(huì)面臨這樣一個(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)之間兩階段提交的問題凯肋。
簡(jiǎ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ù)的淵源。
白日夢(mèng)后續(xù)計(jì)劃還會(huì)有文章中進(jìn)一步跟大家詳細(xì)的分享分布式事務(wù)話題宽闲。
八众眨、再看MySQL兩階段寫日志
那我們?cè)賹⑺悸防氐組ySQL兩階段寫日志的話題握牧。
其實(shí)說到這里,你大概也能直接想到娩梨,其實(shí)上一篇文章中的兩階段提交沿腰,表面上其實(shí)就是兩階段寫入日志。
通過我前面的描述狈定,你也一定知道了兩份日志文件邏輯對(duì)齊的標(biāo)記是有一份相同的XID颂龙。
就是這種兩階段的機(jī)制保證了兩個(gè)日志(在分布式事務(wù)中就是多個(gè)數(shù)據(jù)節(jié)點(diǎn))在邏輯上能達(dá)到一致的效果。
九纽什、留一個(gè)彩蛋
如果你仔細(xì)想一下措嵌,上面第三部分在分享 sync_binlog=1
加餐時(shí),我所描述的示例場(chǎng)景其實(shí)是適用于單機(jī)MySQL的簡(jiǎn)單場(chǎng)景芦缰。
其實(shí)這個(gè)場(chǎng)景還能再復(fù)雜一些企巢!
串聯(lián)MySQL集群、將同步让蕾、半同步浪规、異步的主從復(fù)制關(guān)系以及這里的兩階段提交、日志的落盤時(shí)機(jī)探孝、幽靈事務(wù)罗丰!結(jié)合成一個(gè)場(chǎng)景效果會(huì)更好。
但是我將它放在《為研發(fā)同學(xué)定制的面試指南》排期的后半部分也就是MySQL集群部分再姑。讓我們從易到難過度過去! 歡迎關(guān)注白日夢(mèng)找御。
十元镀、推薦閱讀(公眾號(hào)首發(fā),歡迎關(guān)注白日夢(mèng))
- MySQL的修仙之路霎桅,圖文談?wù)勅绾螌W(xué)MySQL栖疑、如何進(jìn)階!(已發(fā)布)
- 面前突擊滔驶!33道數(shù)據(jù)庫高頻面試題遇革,你值得擁有!(已發(fā)布)
- 大家常說的基數(shù)是什么揭糕?(已發(fā)布)
- 講講什么是慢查萝快!如何監(jiān)控?如何排查著角?(已發(fā)布)
- 對(duì)NotNull字段插入Null值有啥現(xiàn)象揪漩?(已發(fā)布)
- 能談?wù)?date、datetime吏口、time奄容、timestamp冰更、year的區(qū)別嗎?(已發(fā)布)
- 了解數(shù)據(jù)庫的查詢緩存和BufferPool嗎昂勒?談?wù)効矗蜀细。ㄒ寻l(fā)布)
- 你知道數(shù)據(jù)庫緩沖池中的LRU-List嗎?(已發(fā)布)
- 談?wù)剶?shù)據(jù)庫緩沖池中的Free-List戈盈?(已發(fā)布)
- 談?wù)剶?shù)據(jù)庫緩沖池中的Flush-List奠衔?(已發(fā)布)
- 了解臟頁刷回磁盤的時(shí)機(jī)嗎?(已發(fā)布)
- 用十一張圖講清楚奕谭,當(dāng)你CRUD時(shí)BufferPool中發(fā)生了什么涣觉!以及BufferPool的優(yōu)化!(已發(fā)布)
- 聽說過表空間沒血柳?什么是表空間官册?什么是數(shù)據(jù)表?(已發(fā)布)
- 談?wù)凪ySQL的:數(shù)據(jù)區(qū)难捌、數(shù)據(jù)段膝宁、數(shù)據(jù)頁、數(shù)據(jù)頁究竟長(zhǎng)什么樣根吁?了解數(shù)據(jù)頁分裂嗎员淫?談?wù)効矗?已發(fā)布)
- 談?wù)凪ySQL的行記錄是什么?長(zhǎng)啥樣击敌?(已發(fā)布)
- 了解MySQL的行溢出機(jī)制嗎介返?(已發(fā)布)
- 說說fsync這個(gè)系統(tǒng)調(diào)用吧! (已發(fā)布)
- 簡(jiǎn)述undo log、truncate沃斤、以及undo log如何幫你回滾事物! (已發(fā)布)
- 我勸圣蝎!這位年輕人不講MVCC,耗子尾汁衡瓶! (已發(fā)布)
- MySQL的崩潰恢復(fù)到底是怎么回事徘公? (已發(fā)布)
- MySQL的binlog有啥用?誰寫的哮针?在哪里关面?怎么配置 (已發(fā)布)
- MySQL的bin log的寫入機(jī)制 (已發(fā)布)
- 刪庫后!除了跑路還能干什么十厢?(已發(fā)布)