通過一條語(yǔ)句的執(zhí)行,深入理解InnoDB的底層架構(gòu)
MySQL最常用的存儲(chǔ)引擎是innodb,我們今天就借助一條更新語(yǔ)句的執(zhí)行,了解下innodb具體是如何處理的,深入理解下它的架構(gòu)。
假設(shè)更新語(yǔ)句是這樣的:
- update user set name ='xxx' where id = 1;
這條SQL語(yǔ)句發(fā)送到MySQL上后,會(huì)經(jīng)過SQL接口、解析器、優(yōu)化器、執(zhí)行器幾個(gè)階段,解析SQL、生成執(zhí)行計(jì)劃,再由執(zhí)行器調(diào)用存儲(chǔ)引擎執(zhí)行這個(gè)執(zhí)行計(jì)劃。
如下圖所示:
圖1 MySQL底層架構(gòu)
下面我們就跟隨一條update語(yǔ)句,分析下innodb存儲(chǔ)引擎的架構(gòu)設(shè)計(jì)。
1、innodb最重要的組件:緩沖池(BufferPool)
innodb存儲(chǔ)引擎中有一個(gè)非常重要的組件,就是緩沖池(BufferPool),這里面會(huì)緩沖很多數(shù)據(jù),以便于以后操作數(shù)據(jù)的時(shí)候,可以直接操作內(nèi)存,就不用訪問磁盤了。
圖2 innoDB重要組件緩沖池
innoDB執(zhí)行上面那條更新語(yǔ)句的時(shí)候,會(huì)先看id = 1的這條語(yǔ)句是否在緩沖池中,如果不再就需要從磁盤加載到緩沖池來,而且還會(huì)對(duì)這條記錄加獨(dú)占鎖。
鎖相關(guān)的知識(shí)點(diǎn),后面會(huì)有講解,這里不是重點(diǎn),就不展開了。
2、undo日志文件
接下來,準(zhǔn)備更新id = 1的這條數(shù)據(jù)時(shí),會(huì)先把id = 1和name原來的值寫入到undo日志文件中去。
這么做的目的是什么?當(dāng)然是方便回滾了。
MySQL增刪改數(shù)據(jù)都是放在事務(wù)里執(zhí)行的,如果事務(wù)提交失敗了,就可以根據(jù)undo日志進(jìn)行回滾。
圖3 undo日志文件
把id = 1的那條要更新的數(shù)據(jù)加載到緩沖池,把要更新數(shù)據(jù)的舊值寫入undo日志文件后,就可以開始更新這條記錄了。
更新的時(shí)候,先更新緩沖池的數(shù)據(jù)。更新完后,緩沖池里的數(shù)據(jù)就變成:name = 'xxx'了,而此時(shí)磁盤上的數(shù)據(jù)還是name='zhangsan'。此時(shí)innoDB數(shù)據(jù)狀態(tài)就變成這樣了:
圖4 更新緩沖池?cái)?shù)據(jù)
3、redo日志文件
此時(shí)緩沖池和磁盤上的數(shù)據(jù)是不一致的,如果MySQL宕機(jī)了,怎么辦?
此時(shí)MySQL宕機(jī)了,緩沖池里的數(shù)據(jù)肯定就丟失了。
這時(shí)候,就要引入一個(gè)新的組件:redo日志。
redo日志也是一個(gè)內(nèi)存緩沖區(qū),用來存放redo日志的,就是用來記錄你對(duì)數(shù)據(jù)做了那些修改。
比如,id = 1這條記錄,修改了name,redo日志可能就這樣:id = 1, name = 'xxx'。
圖5 redo日志
有了redo log,MySQL宕機(jī)后重啟,就可以恢復(fù)更新后的數(shù)據(jù)。
但是,如果此時(shí)MySQL數(shù)據(jù)庫(kù)宕機(jī)了,會(huì)怎樣?
必然是緩沖池中修改過的數(shù)據(jù),redo log buffer日志都會(huì)丟失。
但是,這也不要緊,因?yàn)槟愀聰?shù)據(jù)的事務(wù)沒有提交,此時(shí)MySQL宕機(jī)了,事務(wù)就執(zhí)行失敗了,客戶端會(huì)收到一個(gè)數(shù)據(jù)庫(kù)異常,MySQL重啟后磁盤上的數(shù)據(jù)還是原樣子。
所以數(shù)據(jù)還是一致的。
另外,redo日志是innoDB特有的一個(gè)組件。
4、提交事務(wù)
上面的步驟完成之后,就要提交事務(wù)了,此時(shí)會(huì)把redo日志刷到磁盤上去。
刷盤策略可以通過innodb_flush_log_at_trx_commit來配置。
這個(gè)配置有幾個(gè)選項(xiàng):
0,提交事務(wù)的時(shí)候,不會(huì)把redo日志刷入磁盤;
1,默認(rèn)值,提交事務(wù)的時(shí)候,會(huì)把redo刷入磁盤,只要事務(wù)提交成功,redo日志就比如進(jìn)入磁盤了。
2,提交事務(wù)的時(shí)候,會(huì)把redo刷入os cache。操作系統(tǒng)會(huì)不定期把os cache里的數(shù)據(jù)刷到磁盤里去。
所以innodb_flush_log_at_trx_commit等于0或2的時(shí)候,redo日志都有事務(wù)提交成功,沒寫進(jìn)磁盤的可能,緩沖池里更新后的數(shù)據(jù)也丟失了。此時(shí)MySQL重啟,就無法根據(jù)redo恢復(fù)更新后的數(shù)據(jù),就會(huì)出現(xiàn)數(shù)據(jù)不一致情況。
所以一般情況下,我們都會(huì)把innodb_flush_log_at_trx_commit配置為1。
圖6 redo日志
5、binlog日志
其實(shí)MySQL中提交事務(wù)的時(shí)候,還會(huì)記錄binlog。binlog是MySQL server自己的日志文件。
redo日志屬于一種偏向于物理性質(zhì)的重做日志,它里面記錄的相當(dāng)于是“對(duì)某某數(shù)據(jù)頁(yè)的某某記錄,做了某某修改”。
binlog叫做歸檔日志,它里面記錄的是偏向于邏輯性的日志,類似于redis的aof日志。
我們提交事物的時(shí)候,除了把redo log日志寫到磁盤,還會(huì)同時(shí)把對(duì)應(yīng)的binlog日志寫到磁盤文件中。
圖7 binlog日志
與redo log日志一樣,binlog日志有兩種刷盤策略,相應(yīng)的配置項(xiàng)為:sync_binlog。
0,默認(rèn)值,提交事務(wù)的時(shí)候,會(huì)把binlog刷入os cache。
1,提交事務(wù)的時(shí)候,會(huì)把binlog寫入磁盤。
所以,當(dāng)sync_binlog設(shè)置為0的時(shí)候,如果機(jī)器宕機(jī),binlog會(huì)有丟失的風(fēng)險(xiǎn)。設(shè)置為1的時(shí)候,即使機(jī)器宕機(jī),binlog日志也不會(huì)丟失。
當(dāng)我們把binlog日志寫入磁盤后,接著就完成了最終的事務(wù)提交,最后會(huì)把本次更新對(duì)應(yīng)的binlog日志文件名和這次更新的binlog日志在文件里的位置,都寫入到redo log日志里去,同時(shí)在redo log日志文件里寫入一個(gè)commit標(biāo)記。
到此為止,一個(gè)事務(wù)提交才是完成了。
圖8 binlog刷到磁盤
最后再補(bǔ)充一點(diǎn),在redo日志中寫入commit標(biāo)識(shí),其目的是保持redo log日志與binlog日志一致的。
也就是說,innoDB根據(jù)commit標(biāo)識(shí)判定一個(gè)事務(wù)是否執(zhí)行成功。如果在圖8的5、6、7步,必須是三個(gè)步驟都執(zhí)行成功了,才算提交了事務(wù)。假如執(zhí)行其中某個(gè)步驟的時(shí)候,機(jī)器宕機(jī)了,會(huì)怎樣?
這時(shí)候,因?yàn)閞edo日志里沒有commit標(biāo)識(shí),所以會(huì)判定此次事務(wù)執(zhí)行不成功,就不會(huì)出現(xiàn)數(shù)據(jù)不一致的情況。
6、后臺(tái)線程把內(nèi)存數(shù)據(jù)刷到磁盤
此時(shí)事務(wù)提交了,已經(jīng)把緩沖池(BufferPool)中的數(shù)據(jù)更新了,磁盤里也有了redo日志和binlog日志,但這時(shí)候,磁盤上的數(shù)據(jù)還是舊的啊。
所以MySQL會(huì)有一個(gè)后臺(tái)IO線程,會(huì)在某個(gè)時(shí)間,隨機(jī)把緩沖池(BufferPool)中的數(shù)據(jù)刷到磁盤上去。
圖9 innoDB執(zhí)行更新語(yǔ)句時(shí)的完整流程
后臺(tái)IO線程把緩沖池的數(shù)據(jù)刷到磁盤前,即使MySQL宕機(jī),也沒關(guān)系,因?yàn)闄C(jī)器重啟后,會(huì)根據(jù)redo日志回復(fù)之前提交事務(wù)所作的修改。
7、總結(jié)
通過一次更新數(shù)據(jù)的流程,了解了innoDB存儲(chǔ)引擎做了哪些工作。更新前記錄undo日志,更新緩沖池(BufferPool)里的數(shù)據(jù),記錄redo log日志,binlog日志,每一步都有其專門的作用,innoDB通過這套復(fù)雜的架構(gòu)設(shè)計(jì),保證了數(shù)據(jù)更新的高性能和一致性。