本篇文章主要介紹 Redo Log 和 Undo Log:
- 利用 Redo Log 和 Undo Log 實現本地事務的原子性、永續性
- Redo Log 的寫回策略
- Redo Log Buffer 的刷盤時機
紀錄檔:Redo Log 和 Undo Log · 語雀 (yuque.com)
通過寫入紀錄檔來保證原子性、永續性是業界的主流做法。
Redo Log 是什麼:Redo Log 被稱為重做紀錄檔。
Undo Log 是什麼:Undo Log 被稱為復原紀錄檔、回滾紀錄檔。
技術是為了解決問題而生的,通過 Redo Log 我們可以實現崩潰恢復,防止資料更新丟失,保證事務的永續性。也就是說,在機器故障恢復後,系統仍然能夠通過 Redo Log 中的資訊,持久化已經提交的事務的操作結果。
技術是為了解決問題而生的,Undo Log 的作用 / 功能:
Undo Log 中儲存了回滾需要的資料。在事務回滾或者崩潰恢復時,根據 Undo Log 中的資訊對提前寫入的資料變動進行擦除。
Redo Log 和 Undo Log 都是用於實現事務的特性,並且都是在儲存引擎層實現的。由於只有 InnoDB 儲存引擎支援事務,因此只有使用 InnoDB 儲存引擎的表才會使用 Redo Log 和 Undo Log。
MySQL 的 InnoDB 儲存引擎使用「Write-Ahead Log」紀錄檔方案實現本地事務的原子性、永續性。
「提前寫入」(Write-Ahead),就是在事務提交之前,允許將變動資料寫入磁碟。與「提前寫入」相反的就是,在事務提交之前,不允許將變動資料寫入磁碟,而是等到事務提交之後再寫入。
「提前寫入」的好處是:有利於利用空閒 I/O 資源。但「提前寫入」同時也引入了新的問題:在事務提交之前就有部分變動資料被寫入磁碟,那麼如果事務要回滾,或者發生了崩潰,這些提前寫入的變動資料就都成了錯誤。「Write-Ahead Log」紀錄檔方案給出的解決辦法是:增加了一種被稱為 Undo Log 的紀錄檔,用於進行事務回滾。
變動資料寫入磁碟前,必須先記錄 Undo Log,Undo Log 中儲存了回滾需要的資料。在事務回滾或者崩潰恢復時,根據 Undo Log 中的資訊對提前寫入的資料變動進行擦除。
「Write-Ahead Log」在崩潰恢復時,會經歷以下三個階段:
以下的執行過程限定在,使用 InnoDB 儲存引擎的表
事務開始
申請加鎖:表鎖、MDL 鎖、行鎖、索引區間鎖(看情況加哪幾種鎖)
執行器找儲存引擎取資料。
執行器拿到儲存引擎給的行資料,進行更新操作後,再呼叫儲存引擎介面寫入這行新資料。
儲存引擎將回滾需要的資料記錄到 Undo Log,並將這個更新操作記錄到 Redo Log,此時 Redo Log 處於 prepare 狀態。並將這行新資料更新到記憶體(innodb_buffer_cache)中。同時,然後告知執行器執行完成了,隨時可以提交事務。
手動事務 commit:執行器生成這個操作的 Binary Log,並把 Binary Log 寫入磁碟。
執行器呼叫儲存引擎的提交事務介面,儲存引擎把剛剛寫入的 Redo Log 改成 commit 狀態。
事務結束
其中第 5 步,將這個更新操作記錄到 Redo Log。生成的 Redo Log 是儲存在 Redo Log Buffer 後就返回,還是必須寫入磁碟後才能返回呢?
這就是 Redo Log 的寫入策略,Redo Log 的寫入策略由 innodb_flush_log_at_trx_commit 引數控制,該引數不同的值對應不同的寫入策略。
還有第 6 步,把 Binary Log 寫入磁碟和 Redo Log 一樣,也有相應的寫回策略,由引數 sync_binlog 控制。
通常我們說 MySQL 的「雙 1」設定,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都設定成 1。也就是說,一個事務提交前,需要等待兩次刷盤,一次是 Redo Log 刷盤(prepare 階段),一次是 Binary Log 刷盤。
在上面【MySQL 中一條 SQL 更新語句的執行過程】部分,最後將 Redo Log 的寫入拆成了兩個步驟:prepare 和 commit,這就是"兩階段提交"。
「兩階段提交」的作用 / 目的:
如果先寫 Redo Log,再寫 Binary Log 或者 先寫 Binary Log,再寫 Redo Log,寫入第一個紀錄檔後,如果此時發生了崩潰,那麼第二個紀錄檔沒有寫入,就造成了兩個紀錄檔的不一致。資料庫的狀態就有可能和用 Binary Log 恢復出來的庫的狀態不一致。備庫利用 Binary Log 進行資料同步,就會出現主備庫資料不一致的問題。具體的講解可以看極客時間的專欄《MySQL實戰45講》
而使用「兩階段提交」,遵守「崩潰恢復時,判斷事務該提交、還是該回滾的規則」,就可以保證兩份紀錄檔(Binary Log 和 Redo Log)之間的邏輯一致。
「崩潰恢復時,判斷事務該提交、還是該回滾的規則」如下:
如果事務寫入 Redo Log 處於 prepare 階段之後、寫 Binary Log 之前,發生了崩潰(也就是時刻 A 發生了崩潰),由於此時 Binary Log 還沒寫,Redo Log 也還沒處於 commit 狀態,所以崩潰恢復的時候,這個事務會回滾。這時 Binary Log 還沒寫,所以也不會傳到備庫。主庫和備庫的資料狀態一致。
如果事務寫入 Binary Log 之後,Redo Log 還沒處於 commit 狀態之前,發生了崩潰(也就是時刻 B 發生了崩潰),根據崩潰恢復時的判斷規則中第 2 條,Redo Log 處於 prepare 階段,Binary Log 完整,所以崩潰恢復的時候,會利用該 Redo Log 中的資訊,持久化事務的操作結果。這時 Binary Log 已經寫了,所以會傳到備庫。主庫和備庫的資料狀態一致。
Binary Log 的寫入在崩潰恢復時,判斷事務該提交還是該回滾時,起到了至關重要的作用,只有 Binary Log 寫入成功才能保證兩份紀錄檔(Binary Log 和 Redo Log)之間的邏輯一致,才能考慮提交。
我們在【MySQL 中一條 SQL 更新語句的執行過程】部分的第 5 步中說:儲存引擎將這行新資料更新到記憶體(innodb_buffer_cache)中。同時,將這個更新操作記錄到 Redo Log,此時 Redo Log 處於 prepare 狀態。然後告知執行器執行完成了,隨時可以提交事務。
生成的 Redo Log 是儲存在 Redo Log Buffer 後就返回,還是必須寫入磁碟後才能返回呢?這就是 Redo Log 的寫入策略。Redo Log 的寫入策略由 innodb_flush_log_at_trx_commit 引數控制。
我們可以通過修改該引數的值,設定 Redo Log 的寫入策略,該引數可選的值有 3 個:
MySQL 的資料目錄(使用 show variables like 'datadir' 檢視)下預設有兩個名為 ib_logfile0 和
ib_logfile1 的檔案,Redo Log Buffer 中的 Redo Log 預設情況下就是重新整理到這兩個磁碟檔案中。
資料目錄的位置也可以通過以下命令檢視:select @@datadir;
如果我們對預設的 Redo Log 檔案組不滿意,可以通過下邊幾個啟動引數來調節:
從上邊的描述中可以看到,磁碟上的 Redo Log 檔案不只一個,而是以一個 紀錄檔檔案組 的形式出現的。這些檔案
以 ib_logfile[數位] ( 數位 可以是 0 、 1 、 2 ...)的形式進行命名。
在將 Redo Log 寫入 紀錄檔檔案組 時,是從 ib_logfile0 開始寫,如果 ib_logfile0 寫滿了,就接著 ib_logfile1 寫,同理, ib_logfile1 寫滿了就去寫 ib_logfile2 ,依此類推。
如果寫到最後一個檔案該怎麼辦呢?那就重新轉到 ib_logfile0 繼續寫,所以整個過程如下圖所示:
總共的 Redo Log 檔案空間大小其實就是:innodb_log_file_size × innodb_log_files_in_group 。(單個檔案的空間大小 * 檔案組中的檔案個數)
如果採用迴圈使用的方式向 Redo Log 檔案組裡寫資料的話,那就會造成追尾,也就是後寫入的 Redo Log 覆蓋掉前邊寫的 Redo Log。為了解決 Redo Log 的覆蓋寫入問題,InnoDB 的設計者提出了 checkpoint 的概念。
InnoDB 的設計者為 Redo Log 定義了多種型別,以應對事務對資料庫的不同修改場景,但是絕大部分型別的 Redo Log 都有下邊這種通用的結構:
各個部分的詳細釋義如下:
在 MySQL 5.7.21 這個版本中,InnoDB 的設計者一共為 Redo Log 設計了 53 種不同的型別。各種型別的 Redo Log 的不同之處在於 data 的具體結構不同。
Redo Log 的具體格式可以看掘金小冊《MySQL 是怎樣執行的:從根兒上理解 MySQL》
這些型別的 Redo Log 既包含 物理 層面的意思,也包含 邏輯 層面的意思,具體指:
總結來說,Redo Log 中記錄的是該操作對哪個表空間的哪個頁的哪個偏移量進行了什麼修改。
一個事務可能包含多條 SQL 語句,每一條 SQL 語句可能包含多個「對底層頁面的操作」,每個「對底層頁面的操作」可能包含多個 Redo Log。這樣的一個「對底層頁面的操作」的過程被稱為 Mini-Transaction,簡稱 mtr。
「對底層頁面的操作」比如說:
我們需要保證一個「對底層頁面的操作」對應的多個 Redo Log 不可分割,即一個「對底層頁面的操作」是原子的,這個操作對應的 Redo Log 要麼都寫入磁碟,要麼都不寫入磁碟。所以 InnoDB 的設計者規定在執行這些需要保證原子性的操作時必須以 組 的形式來記錄 Redo Log,在進行奔潰恢復時,針對某個組中的 Redo Log,要麼把全部的 Redo Log 都恢復掉,要麼一個 Redo Log 也不恢復。
那麼 InnoDB 的設計者是怎麼做到分組的呢?InnoDB 的設計者在一個「對底層頁面的操作」的最後一個 Redo Log 後面加上一個特殊型別的 Redo Log。相當於某個需要保證原子性的操作產生的一系列 Redo Log 必須要以一個特殊型別的 Redo Log 結尾,這樣在奔潰恢復時:
Redo Log Buffer 就是在伺服器啟動時,向作業系統申請的大一片連續的記憶體空間。
這片連續的記憶體空間被劃分為若干個連續的用來儲存 Redo Log 的資料頁。
用來儲存 Redo Log 的資料頁被稱為 Redo Log Block。
我們可以通過啟動引數 innodb_log_buffer_size 來指定 Redo Log Buffer 的大小。
我們前邊說過一個 mtr 執行過程中可能產生若干個 Redo Log ,這些 Redo Log 是一個不可分割的組,所以其實並不是每生成一個 Redo Log,就將其插入到 Redo Log Buffer 中,而是每個 mtr 執行過程中產生的紀錄檔先暫時存到一個地方,當該 mtr 結束的時候,將過程中產生的一組 Redo Log 再全部複製到 Redo Log Buffer 中。
不同的事務可能是並行執行的,所以不同事務的 mtr 可能是交替執行的。每當一個 mtr 執行完成時,伴隨
該 mtr 生成的一組 Redo Log 就需要被複制到 Redo Log Buffer 中,也就是說不同事務的 mtr 可能是交替寫入 Redo Log Buffer 的。
mtr 執行過程中產生的一組 Redo Log 在 mtr 結束時會被複制到 Redo Log Buffer 中,在一些情況下 Redo Log Buffer 中的 Redo Log 會被寫回磁碟,Redo Log 的刷盤時機如下:
Redo Log Buffer 的空間不足時,執行刷盤操作
Redo Log Buffer 的空間是有限的,空間大小由 innodb_log_buffer_size 來指定。
InnoDB 的設計者認為:如果 Redo Log Buffer 的記憶體被佔用 1 / 2,就需要把 Redo Log Buffer 中的 Redo Log 重新整理到磁碟。
一個事務提交時,執行刷盤操作
在前面【Redo Log 的寫入策略】部分,講到我們可以通過設定 innodb_flush_log_at_trx_commit 引數的值,在事務提交時執行刷盤操作後才能返回。
事務提交時執行刷盤操作後才能返回是 Redo Log 的預設寫入策略。
後臺執行緒不停的執行刷盤操作
後臺有一個執行緒,每秒都會執行一次刷盤操作。後臺執行緒執行刷盤操作的頻率可以通過引數設定。
具體通過哪個引數設定,我也不清楚。
正常關閉伺服器時,執行刷盤操作
做 checkpoint 時,執行刷盤操作
等等
MySQL中的 Undo Log 嚴格的講不是 Log,而是資料,因此它的管理和落盤都跟資料是一樣的:
之所以這樣實現,首要的原因是 MySQL 中的 Undo Log 不只是承擔 Crash Recovery 時保證 Atomic 的作用,更需要承擔 MVCC 對歷史版本的管理的作用,設計目標是高事務並行,方便的管理和維護。因此當做資料更合適。
但既然還叫 Log,就還是需要有 Undo Log 的責任,那就是保證 Crash Recovery 時,如果看到資料的修改,一定要能看到其對應 Undo 的修改,這樣才有機會通過事務的回滾保證 Crash Atomic。標準的 Undo Log 這一步是靠 WAL 實現的,也就是要求 Undo 寫入先於資料落盤。而 InnoDB 中 Undo Log 作為一種特殊的資料,這一步是通過 redo 的 min-transaction 保證的,簡單的說就是資料的修改和對應的 Undo 修改,他們所對應的 Redo Log 被放到同一個 min-transaction 中,同一個 min-transaction 中的所有 Redo Log 在 Crash Recovery 時以一個整體進行重放,要麼全部重放,要麼全部丟棄。
作者:CatKang
連結:https://www.zhihu.com/question/267595935/answer/2204949497
來源:知乎
20 | 紀錄檔(下):系統故障,如何恢復資料? (geekbang.org)
MySQL 是怎樣執行的:從根兒上理解 MySQL - 小孩子4919 - 掘金課程 (juejin.cn)
本文來自部落格園,作者:真正的飛魚,轉載請註明原文連結:https://www.cnblogs.com/feiyu2/p/16672433.html