詳解 MySQL 重做日志 redolog
redo log也就是所謂的重做日志,是innoDb存儲引擎獨有的日志,它使得MySQL在宕機情況下依舊可以redo log完成數據具備恢復能力, 從而保證數據完整性,本文將針對該日志進行分析講解,希望對你有幫助。
redolog的作用
redo log是InnoDB存儲引擎獨有的日志,用于MySQL工作過程中崩潰或者宕機時進行數據恢復的文件,從而保證數據的持久性以及完整性。
redolog是如何運行工作的
我們都知道數據庫數據基本單位也是和操作系統一致的,都是以頁為單位,我們以MySQL數據查詢為例,為了盡可能減少IO次數,MySQL在進行數據查詢會優先將數據查詢并存儲到buffer Pool中,在事務提交后將修改操作按照配置的刷盤機制寫回磁盤中。
例如當我們需要對數據修改(update)操作時,基于buffer pool完成高效的數據更新操作后將事務提交,此時我們的redo日志數據就會按照innodb_flush_log_at_trx_commit指定的刷盤機制叫redolog緩存數據刷盤:
默認情況下redo日志對應的緩沖區大小為16M,該變量我們可以通過如下語句查看:
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
對應查詢結果如下:
Variable_name |Value |
----------------------+--------+
innodb_log_buffer_size|16777216|
redolog幾個刷盤時機
上文圖解的第四步提到了redo log刷盤的操作,當符合以下幾種條件時,對應redo log buffer會被刷盤持久化到磁盤中:
- 事務提交:當事務提交時,log buffer里redo log會按照innodb_flush_log_at_trx_commit的刷盤時機將數據持久化到磁盤中。
- redo log buffer空間不足:log buffer中的redo log已經占滿該緩沖區一半時,緩沖區數據就會被刷到磁盤中。
- 事務日志緩沖區已滿:InnoDB使用一個事務日志緩沖區(transaction log buffer)存儲事務redo log的日志條目,當該緩存區已滿時,就會觸發日志刷新將日志寫入磁盤中。
- 后臺線程定時刷盤:innodb后臺線程會每隔1s調用操作系統fsync函數將redolog數據刷盤。
- checkpoint:線程會定時執行一個checkpoint,將buffer pool已經刷盤持久化到物理文件的數據對應的redo log設置為可被覆蓋(保證日志空間可以循環復用),這期間對應的redo log數據就會被寫入磁盤中。
- 服務器關閉:MySQL服務正常關閉時,這些緩沖區的數據就會寫入到磁盤中。
redo log的刷盤策略
上文事務提交時提到一個刷盤策略的概念,實際上寫入磁盤的時機是由MySQL系統參數設置決定的,我們可以鍵入下面這條SQL查看innodb_flush_log_at_trx_commit這個參數的設定值:
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
以筆者的MySQL8為例,默認情況下這個參數值為1:
當這個值為0時,每次進行修改寫入到redo log buffer,然后redo log buffer會將數據寫到page cache中,由log thread每個1s調用操作系統函數fsync將數據寫入到redo.file中。很可能因為服務器崩潰或者宕機導致丟失1s的數據。
1為默認值,當參數值設置為1時, 每次進行修改操作后將數據寫入到redo log buffer中,一旦事務被提交,就會自動調用操作系統函數fsync將數據寫入的磁盤中的redo.file文件中。若設置為這個級別,當服務器宕機,若當前事務沒有提交,這部分數據丟失也無妨,事務提交的話,那么這個操作就會被寫到磁盤中,照樣可以恢復。
配置為2時,每當事務提交后,redo log就會刷入內核緩沖區,這些數據具體何時刷盤則交由操作系統決定,這種情況在MySQL宕機情況下不會造成數據丟失,一旦操作系統崩潰則可能會造成內核緩沖區的redo log數據丟失,導致進行數據備份還原時丟失一部分數據:
redo log的日志文件組
redo log并不是單指一個文件,它是由一組日志文件構成的,如下圖所示,這些文件大小都是一樣的,寫入操作時依次從從1開始寫,文件1寫滿了,就將數據寫到文件2,最后寫到文件4。
redolog通過write pos標記當前寫入的位置,每次完成寫入write pos標志位后移,一旦write pos和checkpoint相遇時就說明文件滿了,此時innodb就會通過讓checkpoint往后移進行一些空間數據擦除,以此來保證一個足夠空間容納新數據。
為什么InnoDB不直接將數據寫入磁盤
頁是操作系統的基本單位,一頁差不多16kb,而我們每次操作的數據可能也就x byte,為了x byte的數據操作將一頁的數據進行同步持久化實在有些大材小用了,所以通過redo log buffer記錄修改內容,通過刷盤策略進行數據刷盤更新,由此提升數據庫的并發能力:
bin.log和redo.log對應的二階段提交
經常有讀者面試被問道的為什么我有了redo.log,你還需要bin log呢?而且這兩個日志我到底要先寫哪個才能保證主從數據庫的一致性呢?
對此我們不妨用反正法來說明:
- 假設我們先寫bin.log,當事務提交后bin.log寫入成功,結果再寫redo.log期間,數據庫掛了。重啟恢復后,主數據庫根據早期redo.log(我們的redo.log沒寫入)恢復到bin log寫入前的樣子,而從數據庫已經根據bin.log同步到了一份數據,最終從數據庫比主數據庫多了一條數據。
- 我們再假設寫redo log,假設事務執行期間我們就寫了redo log,在事務提交之后寫bin log數據庫掛了,我們重啟數據庫后主主庫恢復。主庫根據redo log進行災備恢復,將我們更新的數據同時恢復回來,而從庫根據bin log進行數據同步時,并沒有察覺到主庫剛剛寫入的數據,這就導致了從庫比主庫少了一條數據。
所以MySQL設計者提出了二階段提交的概念,整體步驟為:
- 在事務開始時,先寫redo-log(prepare)。
- 事務提交時,再寫bin log。
- 事務提交成功,再寫redo-log(commit)。
有了這樣一個整體步驟我們不妨用兩種情況來舉個例子演示一下二階段提交如何保證數據一致性。
假設我們有一張user表,這張表只有id、name兩個字段。我們執行如下SQL:
update user set name='aa' where id=1;
假如我們在redo.log提交時數據庫宕機,二階段是如何保證數據一致性的呢?
首先數據庫重啟恢復,然后主庫發現redo.log日志處于prepare而且bin.log也沒有寫入,所以一切恢復到之前的樣子(事務回滾),而從庫對此無感,同步時也是同步成操作失敗之前的樣子,一切風平浪靜:
假如我們bin.log進行commit成功之后數據庫宕機,二階段提交是如何保證數據庫一致性的呢?還是老規矩:
- 數據庫重啟恢復,然后主庫發現bin.log有個commit成功的數據(事務是完整的)
- 然redo.log處于prepare階段,但是我們還是可以根據情況推斷出有個當前主庫有個commit成功的事務,所以redo.log會根據bin.log將redo.log設置為commit
- 從庫已根據主庫的bin.log發現有新增一條新數據,由此同步一條更新數據,雙方都有了一條新數據,數據庫一致性由此保證: