MySQL redo log 的深度解析
redo log也就是所謂的重做日志,是innoDb存儲引擎獨有的日志,它使得MySQL在宕機情況下依舊可以redo log完成數(shù)據(jù)具備恢復(fù)能力, 從而保證數(shù)據(jù)完整性,本文將針對該日志進(jìn)行分析講解,希望對你有幫助。

1. redo log的作用
redo log是InnoDB存儲引擎獨有的日志,用于MySQL工作過程中崩潰或者宕機時進(jìn)行數(shù)據(jù)恢復(fù)的文件,從而保證數(shù)據(jù)的持久性以及完整性。

2. redo log是如何運行工作的
我們都知道數(shù)據(jù)庫數(shù)據(jù)基本單位也是和操作系統(tǒng)一致的,都是以頁為單位,我們以MySQL數(shù)據(jù)查詢?yōu)槔?,為了盡可能減少IO次數(shù),MySQL在進(jìn)行數(shù)據(jù)查詢會優(yōu)先將數(shù)據(jù)查詢并存儲到Buffer Pool中,然后按照一定的調(diào)度規(guī)則將修改操作寫回磁盤中。
當(dāng)我們需要對數(shù)據(jù)修改操作之后,這個修改操作就會優(yōu)先被生成一個redo日志存放到redo log buffer中,最終就會被刷盤并寫入到redo log file中。

默認(rèn)情況下日志對應(yīng)的緩沖區(qū)大小為16M,這里面包括了redo.log的緩沖區(qū),該變量我們可以通過如下語句查看:
SHOW VARIABLES LIKE 'innodb_log_buffer_size';對應(yīng)查詢結(jié)果如下:
Variable_name |Value |
----------------------+--------+
innodb_log_buffer_size|16777216|redo log的刷盤時機
上文圖解的第四步提到了redo log刷盤的操作,當(dāng)符合以下幾種條件時,對應(yīng)redo log buffer會被刷盤持久化到磁盤中:
- 事務(wù)提交:當(dāng)事務(wù)提交時,log buffer里redo log會按照innodb_flush_log_at_trx_commit的刷盤時機將數(shù)據(jù)持久化到磁盤中。
- log buffer空間不足:log buffer中的redo log已經(jīng)占滿該緩沖區(qū)一半時,緩沖區(qū)數(shù)據(jù)就會被刷到磁盤中。
- 事務(wù)日志緩沖區(qū)已滿:InnoDB使用一個事務(wù)日志緩沖區(qū)(transaction log buffer)存儲事務(wù)redo log的日志條目,當(dāng)該緩存區(qū)已滿時,就會觸發(fā)日志刷新將日志寫入磁盤中。
- checkpoint:線程會定時執(zhí)行一個checkpoint,將buffer pool已經(jīng)刷盤對應(yīng)的redo.log設(shè)置為可被覆蓋(保證日志空間可以循環(huán)復(fù)用),這期間對應(yīng)的redo數(shù)據(jù)就會被寫入磁盤中。
- 服務(wù)器關(guān)閉:MySQL服務(wù)正常關(guān)閉時,這些緩沖區(qū)的數(shù)據(jù)就會寫入到磁盤中。
redo log的刷盤策略
上文事務(wù)提交時提到一個刷盤策略的概念,實際上寫入磁盤的時機是由MySQL系統(tǒng)參數(shù)設(shè)置決定的,我們可以鍵入下面這條SQL查看innodb_flush_log_at_trx_commit這個參數(shù)的設(shè)定值:
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';以筆者的MySQL8為例,默認(rèn)情況下這個參數(shù)值為1:

當(dāng)這個值為0時,每次進(jìn)行修改寫入到redo log buffer,然后redo log buffer會將數(shù)據(jù)寫到page cache中,由log thread每個1s調(diào)用操作系統(tǒng)函數(shù)fsync將數(shù)據(jù)寫入到redo.file中。很可能因為服務(wù)器崩潰或者宕機導(dǎo)致丟失1s的數(shù)據(jù)。

1為默認(rèn)值,當(dāng)參數(shù)值設(shè)置為1時, 每次進(jìn)行修改操作后將數(shù)據(jù)寫入到redo log buffer中,一旦事務(wù)被提交,就會自動調(diào)用操作系統(tǒng)函數(shù)fsync將數(shù)據(jù)寫入的磁盤中的redo.file文件中。若設(shè)置為這個級別,當(dāng)服務(wù)器宕機,若當(dāng)前事務(wù)沒有提交,這部分?jǐn)?shù)據(jù)丟失也無妨,事務(wù)提交的話,那么這個操作就會被寫到磁盤中,照樣可以恢復(fù)。

配置為2時,每當(dāng)事務(wù)提交后,redo log就會刷入內(nèi)核緩沖區(qū),這些數(shù)據(jù)具體何時刷盤則交由操作系統(tǒng)決定,這種情況在MySQL宕機情況下不會造成數(shù)據(jù)丟失,一旦操作系統(tǒng)崩潰則可能會造成內(nèi)核緩沖區(qū)的redo log數(shù)據(jù)丟失,導(dǎo)致進(jìn)行數(shù)據(jù)備份還原時丟失一部分?jǐn)?shù)據(jù):

redo log的日志文件組
redo log并不是單指一個文件,它是由一組日志文件構(gòu)成的,如下圖所示,這些文件大小都是一樣的,寫入操作時依次從從1開始寫,文件1寫滿了,就將數(shù)據(jù)寫到文件2,最后寫到文件4。
redolog通過write pos標(biāo)記當(dāng)前寫入的位置,每次完成寫入write pos標(biāo)志位后移,一旦write pos和checkpoint相遇時就說明文件滿了,此時innodb就會通過讓checkpoint往后移進(jìn)行一些空間數(shù)據(jù)擦除,以此來保證一個足夠空間容納新數(shù)據(jù)。

為什么InnoDB不直接將數(shù)據(jù)寫入磁盤
頁是操作系統(tǒng)的基本單位,一頁差不多16kb,而我們每次操作的數(shù)據(jù)可能也就x byte,為了x byte的數(shù)據(jù)操作將一頁的數(shù)據(jù)進(jìn)行同步持久化實在有些大材小用了,所以通過redo log buffer記錄修改內(nèi)容,通過刷盤策略進(jìn)行數(shù)據(jù)輸盤更新,由此提升數(shù)據(jù)庫的并發(fā)能力,

bin.log和redo.log對應(yīng)的二階段提交
經(jīng)常有讀者面試被問道的為什么我有了undo log,你還需要bin log呢?而且這兩個日志我到底要先寫哪個才能保證主從數(shù)據(jù)庫的一致性呢?
對此我們不妨用反正法來說明:
假設(shè)我們先寫bin.log,當(dāng)事務(wù)提交后bin.log寫入成功,結(jié)果再寫redo.log期間,數(shù)據(jù)庫掛了。重啟恢復(fù)后,主數(shù)據(jù)庫工具redo.log恢復(fù)到bin log寫入前的樣子,而從數(shù)據(jù)庫在工具bin.log進(jìn)行數(shù)據(jù)同步時發(fā)現(xiàn)bin log有一條寫入操作,最終從數(shù)據(jù)庫比主數(shù)據(jù)庫多了一條數(shù)據(jù)。

我們再假設(shè)寫redo log,假設(shè)事務(wù)執(zhí)行期間我們就寫了redo log,在事務(wù)提交之后寫bin log數(shù)據(jù)庫掛了,我們重啟數(shù)據(jù)庫后主主庫恢復(fù)。主庫根據(jù)redo log進(jìn)行災(zāi)備恢復(fù),將我們更新的數(shù)據(jù)同時恢復(fù)回來,而從庫根據(jù)bin log進(jìn)行數(shù)據(jù)同步時,并沒有察覺到主庫剛剛寫入的數(shù)據(jù),這就導(dǎo)致了從庫比主庫少了一條數(shù)據(jù)。

所以MySQL設(shè)計者提出了二階段提交的概念,整體步驟為:
- 在事務(wù)開始時,先寫redo-log(prepare)。
- 事務(wù)提交時,再寫bin log。
- 事務(wù)提交成功,再寫redo-log(commit)。

有了這樣一個整體步驟我們不妨用兩種情況來舉個例子演示一下二階段提交如何保證數(shù)據(jù)一致性。
假設(shè)我們有一張user表,這張表只有id、name兩個字段。我們執(zhí)行如下SQL:
update user set name='aa' where id=1;假如我們在redo.log提交時數(shù)據(jù)庫宕機,二階段是如何保證數(shù)據(jù)一致性的呢?
首先數(shù)據(jù)庫重啟恢復(fù),然后主庫發(fā)現(xiàn)redo.log日志處于prepare而且bin.log也沒有寫入,所以一切恢復(fù)到之前的樣子(事務(wù)回滾),而從庫對此無感,同步時也是同步成操作失敗之前的樣子,一切風(fēng)平浪靜:

假如我們bin.log進(jìn)行commit成功之后數(shù)據(jù)庫宕機,二階段提交是如何保證數(shù)據(jù)庫一致性的呢?還是老規(guī)矩:
- 數(shù)據(jù)庫重啟恢復(fù),然后主庫發(fā)現(xiàn)bin.log有個commit成功的數(shù)據(jù)(事務(wù)是完整的)
- 然redo.log處于prepare階段,但是我們還是可以根據(jù)情況推斷出有個當(dāng)前主庫有個commit成功的事務(wù),所以redo.log會根據(jù)bin.log將redo.log設(shè)置為commit
- 從庫已根據(jù)主庫的bin.log發(fā)現(xiàn)有新增一條新數(shù)據(jù),由此同步一條更新數(shù)據(jù),雙方都有了一條新數(shù)據(jù),數(shù)據(jù)庫一致性由此保證:
































