全文建立在MySQL的存儲引擎為InnoDB的基礎上
先看一條SQL如何入庫的:
這是一條很簡單的更新SQL,從MySQL服務端接收到SQL到落盤荣月,先后經(jīng)過了MySQL Server層和InnoDB存儲引擎管呵。
- Server層就像一個產(chǎn)品經(jīng)理,分析客戶的需求哺窄,并給出實現(xiàn)需求的方案捐下。
- InnoDB就像一個基層程序員,實現(xiàn)產(chǎn)品經(jīng)理給出的具體方案萌业。
在MySQL”分析需求坷襟,實現(xiàn)方案“的過程中,還夾雜著內(nèi)存操作和磁盤操作生年,以及記錄各種日志婴程。
他們到底有什么用處?他們之間到底怎么配合的抱婉?MySQL又為什么要分層呢档叔?InnoDB里面的那一塊Buffer Pool又是什么?
我們慢慢分析蒸绩。
分層結(jié)構(gòu)
MySQL為什么要分為Server層和存儲引擎兩層呢蹲蒲?
這個問題官方也沒有給出明確的答案,但是也不難猜侵贵,簡單來說就是為了“解耦”。
Server層和存儲引擎各司其職缘薛,分工明確窍育,用戶可以根據(jù)不同的需求去使用合適的存儲引擎卡睦,多好的設計,對不對漱抓?
后來的發(fā)展也驗證了“分層設計”的優(yōu)越性:MySQL最初搭載的存儲引擎是自研的只支持簡單查詢的MyISAM的前身ISAM表锻,后來與Sleepycat合作研發(fā)了Berkeley DB引擎,支持了事務乞娄。江山代有才人出瞬逊,技術(shù)后浪推前浪,MySQL在持續(xù)的升級著自己的存儲引擎的過程中仪或,遇到了橫空出世的InnoDB确镊,InnoDB的功能強大讓MySQL倍感壓力。
自己的存儲引擎打不過InnoDB怎么辦范删?
打不過就加入蕾域!
MySQL選擇了和InnoDB合作。正是因為MySQL存儲引擎的插件化設計到旦,兩個公司合作的非常順利旨巷,MySQL也在合作后不久就發(fā)布了正式支持nnoDB的4.0版本以及經(jīng)典的4.1版本。
MySQL兼并天下模式也成為MySQL走向繁榮的一個重要因素添忘。這能讓MySQL長久地保持著極強競爭力采呐。時至今日,MySQL依然占據(jù)著極高數(shù)據(jù)庫市場份額搁骑,僅次于王牌數(shù)據(jù)庫Oracle斧吐。
Buffer Pool
在InnoDB里,有一塊非常重要的結(jié)構(gòu)——Buffer Pool靶病。
Buffer Pool是個什么東西呢会通?
Buffer Pool就是一塊用于緩存MySQL磁盤數(shù)據(jù)的內(nèi)存空間。
為什么要緩存MySQL磁盤數(shù)據(jù)呢娄周?
我們通過一個例子說明涕侈,我們先假設沒有Buffer Pool,user表里面只有一條記錄煤辨,記錄的age = 1裳涛,假設需要執(zhí)行三條SQL:
- 事務A:update user set age = 2
- 事務B:update user set age = 3
- 事務C:update user set age = 4
如果沒有Buffer Pool,那執(zhí)行就是這樣的:
從圖上可以看出众辨,每次更新都需要從磁盤拿數(shù)據(jù)(1次IO)端三,修改完了需要刷到磁盤(1次IO),也就是每次更新都需要2次磁盤IO鹃彻。三次更新需要6次磁盤IO郊闯。
而有了Buffer Pool,執(zhí)行就成了這樣:
從圖上可以看出,只需要在第一次執(zhí)行的時候?qū)?shù)據(jù)從磁盤拿到Buffer Pool(1次IO)团赁,第三次執(zhí)行完將數(shù)據(jù)刷回磁盤(1次IO)育拨,整個過程只需要2次磁盤IO,比沒有Buffer Pool節(jié)省了4次磁盤IO的時間欢摄。
當然熬丧,Buffer Pool真正的運轉(zhuǎn)流程沒有這么簡單,具體實現(xiàn)細節(jié)和優(yōu)化技巧還有很多怀挠,由于篇幅有限析蝴,本文不做詳細描述。
我想表達的是:Buffer Pool就是將磁盤IO轉(zhuǎn)換成了內(nèi)存操作绿淋,節(jié)省了時間闷畸,提高了效率。
Buffer Pool是提高了效率沒錯躬它,但是出現(xiàn)了一個問題腾啥,Buffer Pool是基于內(nèi)存的,而只要一斷電冯吓,內(nèi)存里面的數(shù)據(jù)就會全部丟失倘待。
如果斷電的時候Buffer Pool的數(shù)據(jù)還沒來得及刷到磁盤,那么這些數(shù)據(jù)就丟失了嗎组贺?
還是上面的那個例子凸舵,如果三個事務執(zhí)行完畢,在age = 4還沒有刷到磁盤的時候失尖,突然斷電啊奄,數(shù)據(jù)就全部丟掉了:
試想一下,如果這些丟失的數(shù)據(jù)是核心的用戶交易數(shù)據(jù)掀潮,那用戶能接受嗎菇夸?
答案是否定的。
那InnoDB是如何做到數(shù)據(jù)不會丟失的呢仪吧?
今天的第一個日志——redo log登場了庄新。
恢復 - redo log
顧名思義,redo是重做的意思薯鼠,redo log就是重做日志的意思择诈。
redo log是如何保證數(shù)據(jù)不會丟失的呢?
就是在修改之后出皇,先將修改后的值記錄到磁盤上的redo log中羞芍,就算突然斷電了,Buffer Pool中的數(shù)據(jù)全部丟失了郊艘,來電的時候也可以根據(jù)redo log恢復Buffer Pool荷科,這樣既利用到了Buffer Pool的內(nèi)存高效性唯咬,也保證了數(shù)據(jù)不會丟失。
我們通過一個例子說明,我們先假設沒有Buffer Pool,user表里面只有一條記錄信卡,記錄的age = 1娶聘,假設需要執(zhí)行一條SQL:
- 事務A:update user set age = 2
執(zhí)行過程如下:
如上圖,有了redo log之后斥滤,將age修改成2之后将鸵,馬上將age = 2寫到redo log里面,如果這個時候突然斷電內(nèi)存數(shù)據(jù)丟失佑颇,在來電的時候顶掉,可以將redo log里面的數(shù)據(jù)讀出來恢復數(shù)據(jù),用這樣的方式保證了數(shù)據(jù)不會丟失挑胸。
你可能會問痒筒,redo log文件也在磁盤上,數(shù)據(jù)文件也在磁盤上茬贵,都是磁盤操作簿透,何必多此一舉?為什么不直接將修改的數(shù)據(jù)寫到數(shù)據(jù)文件里面去呢解藻?
傻瓜老充,因為redo log是磁盤順序?qū)懀瑪?shù)據(jù)刷盤是磁盤隨機寫螟左,磁盤的順序?qū)懕入S機寫高效的多啊啡浊。
這種先預寫日志后面再將數(shù)據(jù)刷盤的機制,有一個高大上的專業(yè)名詞——WAL(Write-ahead logging)胶背,翻譯成中文就是預寫式日志巷嚣。
雖然磁盤順序?qū)懸呀?jīng)很高效了,但是和內(nèi)存操作還是有一定的差距钳吟。
那么廷粒,有沒有辦法進一步優(yōu)化一下呢?
答案是可以砸抛。那就是給redo log也加一個內(nèi)存buffer评雌,也就是redo log buffer,用這種套娃式的方法進一步提高效率直焙。
redo log buffer具體是怎么配合刷盤呢景东?
在這個問題之前之前,我們先來捋一下MySQL服務端和操作系統(tǒng)的關(guān)系:
MySQL服務端是一個進程奔誓,它運行于操作系統(tǒng)之上斤吐。也就是說搔涝,操作系統(tǒng)掛了MySQL一定掛了,但是MySQL掛了操作系統(tǒng)不一定掛和措。
所以MySQL掛了有兩種情況:
- MySQL掛了庄呈,操作系統(tǒng)也掛了,也就是常說的服務器宕機了派阱。這種情況Buffer Pool里面的數(shù)據(jù)會全部丟失诬留,操作系統(tǒng)的os cache里面的數(shù)據(jù)也會丟失。
- MySQL掛了贫母,操作系統(tǒng)沒有掛文兑。這種情況Buffer Pool里面的數(shù)據(jù)會全部丟失,操作系統(tǒng)的os cache里面的數(shù)據(jù)不會丟失腺劣。
OK绿贞,了解了MySQL服務端和操作系統(tǒng)的關(guān)系之后,再來看redo log的落盤機制橘原。redo log的刷盤機制由參數(shù)innodb_flush_log_at_trx_commit控制籍铁,這個參數(shù)有3個值可以設置:
- innodb_flush_log_at_trx_commit = 1:實時寫,實時刷
- innodb_flush_log_at_trx_commit = 0:延遲寫趾断,延遲刷
- innodb_flush_log_at_trx_commit = 2:實時寫拒名,延遲刷
寫可以理解成寫到操作系統(tǒng)的緩存(os cache),刷可以理解成把操作系統(tǒng)里面的緩存刷到磁盤歼冰。
這三種策略的區(qū)別靡狞,我們分開討論:
innodb_flush_log_at_trx_commit = 1:實時寫,實時刷
這種策略會在每次事務提交之前隔嫡,每次都會將數(shù)據(jù)從redo log刷到磁盤中去甸怕,理論上只要磁盤不出問題,數(shù)據(jù)就不會丟失腮恩。
總結(jié)來說梢杭,這種策略效率最低,但是丟數(shù)據(jù)風險也最低秸滴。
innodb_flush_log_at_trx_commit = 0:延遲寫武契,延遲刷
這種策略在事務提交時,只會把數(shù)據(jù)寫到redo log buffer中荡含,然后讓后臺線程定時去將redo log buffer里面的數(shù)據(jù)刷到磁盤咒唆。
這種策略是最高效的,但是我們都知道释液,定時任務是有間隙的全释,但是如果事務提交后,后臺線程沒來得及將redo log刷到磁盤误债,這個時候不管是MySQL進程掛了還是操作系統(tǒng)掛了浸船,這一部分數(shù)據(jù)都會丟失妄迁。
總結(jié)來說這種策略效率最高,丟數(shù)據(jù)的風險也最高李命。
innodb_flush_log_at_trx_commit = 2:實時寫登淘,延遲刷
這種策略在事務提交之前會把redo log寫到os cache中,但并不會實時地將redo log刷到磁盤封字,而是會每秒執(zhí)行一次刷新磁盤操作黔州。
這種情況下如果MySQL進程掛了,操作系統(tǒng)沒掛的話阔籽,操作系統(tǒng)還是會將os cache刷到磁盤辩撑,數(shù)據(jù)不會丟失,如下圖:
但如果MySQL所在的服務器掛掉了仿耽,也就是操作系統(tǒng)都掛了,那么os cache也會被清空各薇,數(shù)據(jù)還是會丟失项贺。如下圖:
所以,這種redo log刷盤策略是上面兩種策略的折中策略峭判,效率比較高开缎,丟失數(shù)據(jù)的風險比較低,絕大多情況下都推薦這種策略林螃。
總結(jié)一下奕删,redo log的作用是用于恢復數(shù)據(jù),寫redo log的過程是磁盤順序?qū)懥迫希腥N刷盤策略完残,有innodb_flush_log_at_trx_commit 參數(shù)控制,推薦設置成2横漏。
回滾 - undo log
我們都知道谨设,InnoDB是支持事務的,而事務是可以回滾的缎浇。
假如一個事務將age=1修改成了age=2扎拣,在事務還沒有提交的時候,后臺線程已經(jīng)將age=2刷入了磁盤素跺。這個時候二蓝,不管是內(nèi)存還是磁盤上,age都變成了2指厌,如果事務要回滾刊愚,找不到修改之前的age=1,無法回滾了仑乌。
那怎么辦呢百拓?
很簡單琴锭,把修改之前的age=1存起來,回滾的時候根據(jù)存起來的age=1回滾就行了衙传。
MySQL確實是這么干的决帖!這個記錄修改之前的數(shù)據(jù)的過程,叫做記錄undo log蓖捶。undo翻譯成中文是撤銷地回、回滾的意思,undo log的主要作用也就是回滾數(shù)據(jù)俊鱼。
如何回滾呢刻像?看下面這個圖:
MySQL在將age = 1修改成age = 2之前,先將age = 1存到undo log里面去并闲,這樣需要回滾的時候细睡,可以將undo log里面的age = 1讀出來回滾。
需要注意的是帝火,undo log默認存在全局表空間里面溜徙,你可以簡單的理解成undo log也是記錄在一個MySQL的表里面,插入一條undo log和插入一條普通數(shù)據(jù)是類似犀填。也就是說蠢壹,寫undo log的過程中同樣也是要寫入redo log的。
歸檔 - binlog
undo log記錄的是修改之前的數(shù)據(jù)九巡,提供回滾的能力图贸。
redo log記錄的是修改之后的數(shù)據(jù),提供了崩潰恢復的能力冕广。
那binlog是干什么的呢疏日?
binlog記錄的是修改之后的數(shù)據(jù),用于歸檔佳窑。
和redo log日志類似制恍,binlog也有著自己的刷盤策略,通過sync_binlog參數(shù)控制:
- sync_binlog = 0 :每次提交事務前將binlog寫入os cache神凑,由操作系統(tǒng)控制什么時候刷到磁盤
- sync_binlog =1 :采用同步寫磁盤的方式來寫binlog净神,不使用os cache來寫binlog
- sync_binlog = N :當每進行n次事務提交之后,調(diào)用一次fsync將os cache中的binlog強制刷到磁盤
那么問題來了溉委,binlog和redo log都是記錄的修改之后的值鹃唯,這兩者有什么區(qū)別呢?有redo log為什么還需要binlog呢瓣喊?
首先看兩者的一些區(qū)別:
- binlog是邏輯日志坡慌,記錄的是對哪一個表的哪一行做了什么修改;redo log是物理日志藻三,記錄的是對哪個數(shù)據(jù)頁中的哪個記錄做了什么修改洪橘,如果你還不了解數(shù)據(jù)頁跪者,你可以理解成對磁盤上的哪個數(shù)據(jù)做了修改。
- binlog是追加寫熄求;redo log是循環(huán)寫渣玲,日志文件有固定大小,會覆蓋之前的數(shù)據(jù)弟晚。
- binlog是Server層的日志忘衍;redo log是InnoDB的日志。如果不使用InnoDB引擎卿城,是沒有redo log的枚钓。
但說實話,我覺得這些區(qū)別并不是redo log不能取代binlog的原因瑟押,MySQL官方完全可以調(diào)整redo log讓他兼并binlog的能力搀捷,但他沒有這么做,為什么呢多望?
我認為不用redo log取代binlog最大的原因是“沒必要”指煎。
為什么這么說呢?
第一點便斥,binlog的生態(tài)已經(jīng)建立起來。MySQL高可用主要就是依賴binlog復制威始,還有很多公司的數(shù)據(jù)分析系統(tǒng)和數(shù)據(jù)處理系統(tǒng)枢纠,也都是依賴的binlog。取代binlog去改變一個生態(tài)費力了不討好黎棠。
第二點晋渺,binlog并不是MySQL的瓶頸,花時間在沒有瓶頸的地方?jīng)]必要脓斩。
總結(jié)
總結(jié)一下:
- Buffer Pool是MySQL進程管理的一塊內(nèi)存空間木西,有減少磁盤IO次數(shù)的作用。
- redo log是InnoDB存儲引擎的一種日志随静,主要作用是崩潰恢復八千,有三種刷盤策略,有innodb_flush_log_at_trx_commit 參數(shù)控制燎猛,推薦設置成2恋捆。
- undo log是InnoDB存儲引擎的一種日志,主要作用是回滾重绷。
- binlog是MySQL Server層的一種日志沸停,主要作用是歸檔。
- MySQL掛了有兩種情況:操作系統(tǒng)掛了MySQL進程跟著掛了昭卓;操作系統(tǒng)沒掛愤钾,但是MySQL進程掛了瘟滨。
最后,再用一張圖總結(jié)一下全文的知識點:
寫在最后
這篇文章寫在一年之前能颁,本來覺得是一篇水文沒想要發(fā)杂瘸,最近無聊修改了一下發(fā)了出來,希望能夠用動圖的形式幫助到MySQL基礎不太好的朋友劲装,大神忽略就好胧沫。
需要強調(diào)的一點是,由于作者水平有限占业,本文只是淺顯的從無到有地闡述了MySQL幾種日志的大致作用绒怨,過程中省略了很多細節(jié),比如Buffer Pool的實現(xiàn)細節(jié)谦疾,比如undo log和MVCC的關(guān)系南蹂,比如binlog buffer、change buffer的存在念恍,比如redo log的兩階段提交六剥。
如果您有任何問題,我們可以探討峰伙,如果您在文中發(fā)現(xiàn)錯誤疗疟,還望您指出,萬分感謝瞳氓!
好了策彤,今天的文章就到這里了。
感謝你的閱讀匣摘!我是CoderW店诗,我們下期再見。
最后音榜,歡迎關(guān)注我的公眾號“CoderW”一起探討進步~~~~
參考資料
- 《MySQL實戰(zhàn)45講》
- 《從根兒上理解MySQL》
- 《MySQL技術(shù)內(nèi)幕—InnoDB存儲引擎》第2版