Mysql 中寫操作時保駕護航的三兄弟!

  • 2021 年 1 月 26 日
  • 筆記

這期的文章主要是講述寫操作過程中涉及到的三個日誌文件,看過前幾期的話可能你或多或少已經有些了解了(或者從別的地方也了解過)。比如整個寫操作過程中用到的兩階段提交,又或者是操作過程中涉及到的日誌文件,但是總體來說不是很系統更談不上全面。

今天我們就來會會這三兄弟。

圖註:思維導圖

兩階段提交

這個名詞你應該聽到過很多次了,在這裡再介紹下這位老朋友。

所謂的兩階段提交,從字面意思來看應該是有兩個步驟來進行約束的。事實上也是如此。這兩個步驟中的主角就是我們今天要講的重要角色中的兩位:binlog 和 redo log

提到兩階段提交,SQL 語句的執行流程就繞不過去了。沒轍,雖然提了很多遍,但還得再拉出來溜溜。只不過這次的側重點和前面的會有些不同。

具體到操作流程上是這樣的:

當執行某個寫操作的 SQL 時,引擎將這行數據更新到內存的同時把對應的操作記錄到 redo log 裏面,然後處於 prepare 狀態。並把完成信息告知給執行器。

執行器生成對應操作的 binlog,並把 binlog 寫入磁盤裡。然後調用引擎的提交事務接口,變更 redo log 狀態為 commit,這樣操作就算完成了。

好了,知道了兩階段提交後,我們接下來看看這些日誌文件的真面目。 

重做日誌(redo log)

首先出場的是位於存儲引擎層的 redo log,它是用來記錄在”數據頁做了什麼修改”的物理日誌文件。

WAL 技術

提到 redo log,WAL 技術必然是繞不過去的,全稱是 Write-Ahead Logging。也就是在同步磁盤前先寫日誌,然後系統再根據一定的策略將日誌里的記錄同步到磁盤裡。

存在的必要性

從上邊的兩階段提交的過程里,我們可以看到 WAL 技術的使用場景。不知道你有沒有疑惑,為什麼中間非要寫 redo log,直接將更新結果同步到磁盤裡不行嗎?傻孩子,同步到磁盤裡就意味着每次寫操作就得產生隨機寫盤操作,速度得多慢啊。

機智的你可能會說了,那我能不能一定的時間後從內存再同步到磁盤裡,這種方式不行嗎?來,先給你個腦瓜崩,你想想,我服務重啟了,這些數據還在不?內存是易失的,不知道什麼異常情況就會導致數據丟失。所以這時候就需要一個能持久化的中間文件,起到”緩衝”的作用,並且寫入速度還不慢。

那麼 redo log 就應運而生了。雖然同樣存儲在磁盤上,但是順序寫入在速度上並不受影響(疑惑的同學可以了解下磁盤的隨機與順序讀寫的區別)。

當然 redo log 除了能起到”延遲”同步磁盤文件的作用外,在數據庫服務器宕機時,還可以用來恢複數據。

寫入時機 

談到寫入時機,是不是更疑惑了,難倒不是更新完內存就寫入 redo log 文件嗎?答案確實不是,因為中間還有一個 redo log buffer(內存中) 。Mysql 每執行一條語句,會先將記錄寫入 redo log buffer,後續執行 commit 操作時會以一定的時機寫入到 redo log 文件(磁盤上)中。

值得注意的是,redo log buffer 里的數據是在執行 commit 操作時寫入到 redo log 文件中的。

至於寫入的時機,則由下面的參數來控制的: 

 

 

(圖片源自網絡)

寫入方式 

知道了寫入時機,這裡簡單介紹下寫入的方式吧。在 Innodb 中,redo log 的大小是固定的,那麼就只能是以循環的方式進行寫入了。假如當前我有 4 個文件,從第一個文件開始寫入,直到最後一個文件寫滿為止,再回到開頭將數據同步至文件後擦除掉繼續寫。

圖中的 write pos 表示當前記錄的位置,隨着不斷寫入逐漸後移。當寫到 ib_logfile_3號文件時,整個 redo log 就被寫滿了。此時更新操作就會被阻塞。系統根據 check point 標記位來擦除掉一些記錄(當然前提是把這些記錄同步至磁盤)。

總得來看 redo log 的寫入方式就是一個不斷寫入,寫滿後擦除,又寫入的過程。

二進制日誌(binlog) 

說完了 redo log,我們再來看看另一個位於服務層的二進制日誌文件 binlog,這位大兄弟扮演的角色是存儲邏輯日誌的,所謂的邏輯日誌就是指修改了什麼,都會記錄其中。

例如:對 id = 1 的字段進行更新操作。

當然除了記錄操作過程外,它還有支持主從同步數據異常恢復的能力。

寫入模式 

binlog 中有三種寫入模式,我們分別來看下有什麼不同及對應的優缺點:

(圖片源自網絡)

寫入方式

與 redo log 循環寫不同的是, binlog 採用追加的方式寫入,當一個文件寫到一定大小後就會切換到另一個。

與 redo log 的關聯

在上面的兩階段提交里我們有提到過在寫入binlog 後會調用引擎的提交事務接口,變更 redo log 狀態為 commit。那麼它是如何找到對應的記錄,或者換句話說,它們兩者是怎麼關聯起來的呢?

答案是通過一個共同的字段 XID,不僅在事務提交時,在崩潰恢復的時候如果遇到僅寫入 prepare 而沒有 commit 的 redo log,也可以通過 XID 去尋找對應的事務。

回顧下寫流程

到這裡我們有必要回顧下寫流程的操作,以更新某個字段為例:

回滾日誌(undo log) 

到這裡,你可能會疑惑了,通篇里哪有 undo log 的影子,你個渣男!

別急,來了!

根據字面意思,你應該能猜出來它是幹啥的。回滾嘛,也就是給你一次後悔的機會。在進行數據修改時,同時記錄 undo log,即同時記錄相反操作的邏輯日誌。你可以理解為操作 update 的時候,寫一條對應相反的 update 記錄,操作 delete 的時候,寫一條對應的 insert 記錄。 

當事務回滾時。從 undo log 中讀取到對應的邏輯記錄就可以進行回滾操作了。

總結

兩階段提交

  • 兩階段提交過程中,更新內存的同時把對應操作記錄到 redo log 中,並把生成的binlog 寫入磁盤後提交事務。

重做日誌 

  • redo log 是位於存儲引擎層的物理日誌,用來記錄在「數據頁做了什麼修改」的物理日誌文件。採用循環寫的方式,記錄數據被修改後的樣子。同時還提供數據恢復的能力。

二進制日誌

  • binlog是位於服務層的邏輯日誌,用來記錄「對數據做了什麼修改」的日誌文件。與 redo log 不同的是,可以一直進行追加寫入。同時還提供主從同步及數據異常恢復的能力。

回滾日誌 

  • 在數據修改時,同時記錄 undo log,可以確保在事務回滾操作時進行數據還原。

關於作者

作者:大家好,我是萊烏,BAT搬磚工一枚。從小公司進入大廠,一路走來收穫良多,想將這些經驗分享給有需要的人,因此創建了公眾號【IT界農民工】。定時更新,希望能幫助到你。同時,我給大家肝了一份Redis面經手冊,在我的公眾號內回復【pdf】即可獲取,希望對你有所幫助。