天天看點

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

作者:Java聯盟盟主

引言

日志日志,在我們平時開發中主要的用途在于監控、備份,但在MySQL中,日志的功能遠遠不止這些,分别有用于記錄的慢查詢日志,復原版本的undolog,當機恢複的redolog、全量備份的binlog等等,而這些日志,也剛好是我們事務的原理

本篇速覽腦圖

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

undolog -- 原子性

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

復原日志,記錄資料被修改前的資訊,屬于邏輯日志

  • 什麼是邏輯日志?

比如我們執行一條delete語句,undolog裡邊記錄的是相反的操作insert記錄【相當于存放的是操作邏輯語句,而不是資料】

‍♂️邏輯日志好處

  1. 比如全表更新,如果是實體日志,我們需要把全表的資料都存下來 若是邏輯日志,隻需要存放一條語句就可以恢複了

undolog用處

復原

一個事務在執行過程中,在還沒有送出事務之前,如果MySQL 發生了崩潰,要怎麼復原到事務之前的資料呢?

  • 在事務沒送出之前,MySQL 會先記錄更新前的資料到 undo log 日志檔案裡面,當事務復原時,可以利用 undo log 來進行復原。

版本鍊

類似一個連結清單,通過復原指針,串聯起來

一條記錄的每一次更新操作産生的 undo log 格式都有一個 roll_pointer 指針和一個 trx_id 事務id:

  • 通過 trx_id 可以知道該記錄是被哪個事務修改的;
  • 通過 roll_pointer 指針可以将這些 undo log 串成一個連結清單,這個連結清單就被稱為版本鍊;
MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

MVCC

通過 ReadView + undo log 實作 MVCC

具體看我的另一篇部落格MVCC

redolog -- 持久性

重做日志,實體記錄

redo log 是實體日志,記錄内容是“在某個資料頁上做了什麼修改”,屬于 InnoDB 存儲引擎。

前置知識-- Buffer Pool

Buffer Pool是很經典的緩存池,其中又以Page為機關

  1. 空閑頁
  2. 幹淨頁
  3. 髒頁【類似作業系統中,修改位為1】
MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

正常同步

首先我們要知道,每次事務送出後,首先是跟 Buffer pool 打交道

  1. 若緩存中沒有我們要操作的資料【類似缺頁中斷,MySQL 中資料是以頁為機關】,則會啟動背景線程,去磁盤中拉取過來
  2. 之後就都是直接操作緩存了
  • 現在緩存的内容更新好了,但磁盤的内容還是舊的,何時更新到磁盤呢?

我們可能會有如下方案:

  1. 每隔一段時間,就同步到磁盤

弊端

每次更新都要寫入磁盤,磁盤IO很高

如果每一次的更新操作都需要寫進磁盤,然後磁盤也要找到對應的那條記錄,然後再更新,整個過程 IO 成本、查找成本都很高。 不如先把更新的操作,記錄在redolog日志裡邊,之後在适當的時候,再一起刷盤!【而不是每次更新就一條一條的刷】

這就是MySQL的 WAL 技術,(Write-Ahead Logging) 先寫進日志,再刷入磁盤

buffer挂掉

一般情況下都沒什麼問題,但可能在執行某次更新操作的時候,buffer挂掉了呢?此時同步過程就GG了,導緻緩存不一緻

  • 是以,為了雙保險,我們還會把資料相關的變化【實體修改】存到redolog裡邊,用來實作事務的持久性【當機後還能查詢redolog,來進行恢複】

redolog保險出場

當事務送出時,會把“在某個資料頁上做了什麼修改”記錄到重做日志緩存(redo log buffer)裡,接着刷盤到 redo log 檔案裡。

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

緩存redolog -- Log Buffer

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

刷盤時機

MySQL 正常關閉時

當 redo log buffer 中記錄的寫入量大于 redo log buffer 記憶體空間的一半時,會觸發落盤

每次事務送出時【具體不同政策不一樣】

一般情況下,事務一送出就會進行刷盤操作。

InnoDB 存儲引擎為 redo log 的刷盤政策提供了 innodb_flush_log_at_trx_commit 參數,它支援三種政策:

  • 0 :設定為 0 的時候,表示每次事務送出時不進行刷盤操作
  • 1 :設定為 1 的時候,表示每次事務送出時都将進行刷盤操作(預設值)
  • 2 :設定為 2 的時候,表示每次事務送出時都隻把 redo log buffer 内容寫入 page cache
MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

背景線程

InnoDB 存儲引擎有一個背景線程,每隔1 秒,就會把 redo log buffer 中的内容寫到檔案系統緩存(page cache),然後調用 fsync 刷盤。

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

不同參數對應情況

innodb_flush_log_at_trx_commit 設定為不同值時,分别是什麼時候将 redo log 寫入磁盤?

  1. 參數0:事務送出時不刷盤,而是靠背景線程,把緩存在 redo log buffer 中的 redo log ,通過調用 write() 寫到作業系統的 Page Cache,然後調用 fsync() 持久化到磁盤。是以參數為 0 的政策,MySQL 程序的崩潰會導緻上一秒鐘所有事務資料的丢失;
  2. 參數1:隻要事務送出成功,redo log記錄就一定在硬碟裡,不會有任何資料丢失。

如果事務執行期間MySQL挂了或當機,這部分日志丢了,但是事務并沒有送出,是以日志丢了也不會有損失!!

  1. 參數2:隻要事務送出成功,redo log buffer中的内容都會寫入檔案系統緩存(page cache)。調用 fsync,将緩存在**作業系統中 Page Cache **裡的 redo log 持久化到磁盤。

如果僅僅隻是MySQL挂了不會有任何資料丢失,隻有作業系統崩潰的情況下,上一秒鐘所有事務資料才可能丢失。

如何選擇參數

  • 資料安全性:參數 1 > 參數 2 > 參數 0
  • 寫入性能:參數 0 > 參數 2> 參數 1
  • 在一些對資料安全性要求比較高的場景中,顯然 innodb_flush_log_at_trx_commit 參數需要設定為 1。
  • 在一些可以容忍資料庫崩潰時丢失 1s 資料的場景中,我們可以将該值設定為 0,這樣可以明顯地減少日志同步到磁盤的 I/O 操作。
  • 安全性和性能折中的方案就是參數 2,雖然參數 2 沒有參數 0 的性能高,但是資料安全性方面比參數 0 強,因為參數 2 隻要作業系統不當機,即使資料庫崩潰了,也不會丢失資料,同時性能友善比參數 1 高。

好處

  1. redolog是檔案追加的形式,而緩存同步到磁盤是随機IO

可以說這是 WAL 技術的另外一個優點:MySQL 的寫操作從磁盤的「随機寫」變成了「順序寫」

總結

redolog的用處:

  1. 實作事務的持久性
  2. 将寫操作從磁盤的「随機寫」變成了「順序寫」

redolog日志檔案組 -- 應對寫滿的情況

硬碟上存儲的 redo log 日志檔案不止一個,而是以一個日志檔案組的形式出現的,每個的redo日志檔案大小都是一樣的。、、、中止這種做做做做做做做做做做做做做做做做做做做做做做做做做做做做錯錯錯

比如可以配置為一組4個檔案,每個檔案的大小是 1GB,整個 redo log 日志檔案組可以記錄4G的内容。 它采用的是環形數組形式,從頭開始寫,寫到末尾又回到頭循環寫,如下圖所示。

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

除了寫入,我們還需要擦除【redolog刷盤到磁盤後,就可以進行擦除了】,是以我們用兩個指針來表示:

  1. write pos:目前記錄寫到的位置
  2. checkpoint:目前要擦除的位置
跟循環隊列的設計有些神似,維護兩個指針,head跟tail一般

這兩個指針把整個環形劃成了幾部分

  1. write pos - checkpoint:待寫入的部分
  2. checkpoint - write pos:還未刷入磁盤的記錄

若write pos追上了checkpoint,說明沒有空間寫入了【跟我們隊列滿了是一樣的情況】,這時候不能再寫入新的 redo log 記錄,MySQL 得停下來,清空一些記錄,把 checkpoint 推進一下。

binlog

redo log 它是實體日志,記錄内容是“在某個資料頁上做了什麼修改”,屬于 InnoDB 存儲引擎。 而 binlog 是邏輯日志,記錄内容是語句的原始邏輯,類似于“給 ID=2 這一行的 c 字段加 1”,屬于MySQL Server 層。 不管用什麼存儲引擎,隻要發生了表資料更新,都會産生 binlog 日志。

與redolog差別

這兩個日志有四個差別

1、适用對象不同:

  • binlog 是 MySQL 的 **Server 層 **實作的日志,所有存儲引擎都可以使用;
  • redo log 是 Innodb 存儲引擎實作的日志;

2、檔案格式不同:

  • binlog 有 3 種格式類型,分别是 STATEMENT(預設格式)、ROW、 MIXED,差別如下:
    • STATEMENT:每一條修改資料的 SQL 都會被記錄到 binlog 中(相當于記錄了邏輯操作,是以針對這種格式, binlog 可以稱為邏輯日志),主從複制中 slave 端再根據 SQL 語句重制。
但 STATEMENT 有動态函數的問題

比如:

update_time=now()//這裡會擷取目前系統時間
複制代碼           

直接執行會導緻與原庫的資料不一緻,是以我們引入了row

  • ROW:記錄行資料最終被修改成什麼樣了(這種格式的日志,就不能稱為邏輯日志了),不會出現 STATEMENT 下動态函數的問題。
但也有缺點

每行資料的變化結果都會被記錄,比如:

update user set name ='melo';
複制代碼           

更新多少行資料就會産生多少條記錄,使 binlog 檔案過大,而在 STATEMENT 格式下隻會記錄一個 update 語句而已;

  • MIXED:這是目前MySQL預設的日志格式,即混合了STATEMENT 和 ROW兩種格式。預設情況下采用STATEMENT,但是在一些特殊情況下采用ROW來進行記錄。MIXED 格式能盡量利用兩種模式的優點,而避開他們的缺點。
MySQL會判斷這條SQL語句是否可能引起資料不一緻,如果是,就用row格式,否則就用statement格式。
  • redo log 是實體日志,記錄的是在某個資料頁做了什麼修改。

3、寫入方式不同:

  • binlog 是追加寫,寫滿一個檔案,就建立一個新的檔案繼續寫,不會覆寫以前的日志,儲存的是全量的日志。
  • redo log 是循環寫,日志空間大小是固定的,全部寫滿就從頭開始,儲存未被刷入磁盤的髒頁日志。

4、用途不同:

  • binlog 用于備份恢複、主從複制;
  • redo log 用于掉電等故障恢複。

如果不小心整個資料庫的資料被删除了,能使用 redo log 檔案恢複資料嗎? 不可以使用 redo log 檔案恢複,隻能使用 binlog 檔案恢複。 因為 redo log 檔案是循環寫,是會邊寫邊擦除日志的,隻記錄未被刷入磁盤的資料的實體日志,已經刷入磁盤的資料都會從 redo log 檔案裡擦除。

binlog 檔案儲存的是全量的日志,也就是儲存了所有資料變更的情況,理論上隻要記錄在 binlog 上的資料,都可以恢複,是以如果不小心整個資料庫的資料被删除了,得用 binlog 檔案恢複資料。

寫入時機

事務執行過程中,先把日志寫到 binlog cache(Server 層的 cache),事務送出的時候,再把 binlog cache 寫到 binlog 檔案中。

因為一個事務的binlog不能被拆開,無論這個事務多大,也要確定一次性寫入,是以系統會給每個線程配置設定一個塊記憶體作為binlog cache。

參數 binlog_cache_size 用于控制單個線程内 binlog cache 所占記憶體的大小。如果超過了這個參數規定的大小,就要暫存到磁盤(Swap)。

什麼時候 binlog cache 會寫到 binlog 檔案?

  • 在事務送出的時候,執行器把 binlog cache 裡的完整事務寫入到 binlog 檔案中,并清空 binlog cache。如下圖:

雖然每個線程有自己 binlog cache,但是最終都寫到同一個 binlog 檔案:

  • 圖中的 write,指的就是指把日志寫入到 binlog 檔案,但是并沒有把資料持久化到磁盤,因為資料還緩存在檔案系統的 page cache 裡,write 的寫入速度還是比較快的,因為不涉及磁盤 I/O。
  • 圖中的 fsync,才是将資料持久化到磁盤的操作,這裡就會涉及磁盤 I/O,是以頻繁的 fsync 會導緻磁盤的 I/O 升高。

MySQL提供一個 sync_binlog 參數來控制資料庫的 binlog 刷到磁盤上的頻率:

  • sync_binlog = 0 的時候,表示每次送出事務都隻 write,不 fsync,後續交由作業系統決定何時将資料持久化到磁盤;
  • sync_binlog = 1 的時候,表示每次送出事務都會 write,然後馬上執行 fsync;
  • sync_binlog =N(N>1) 的時候,表示每次送出事務都 write,但累積 N 個事務後才 fsync。

在MySQL中系統預設的設定是 sync_binlog = 0,也就是不做任何強制性的磁盤重新整理指令,這時候的性能是最好的,但是風險也是最大的。因為一旦主機【指的是作業系統】發生異常重新開機,還沒持久化到磁盤的資料就會丢失。

而當 sync_binlog 設定為 1 的時候,是最安全但是性能損耗最大的設定。因為當設定為 1 的時候,即使主機發生異常重新開機,最多丢失一個事務的 binlog,而已經持久化到磁盤的資料就不會有影響,不過就是對寫入性能影響太大。

如果能容許少量事務的 binlog 日志丢失的風險,為了提高寫入的性能,一般會 sync_binlog 設定為 100~1000 中的某個數值。

兩階段送出

為何需要兩階段送出

主要是因為:redolog影響的是主庫,而binlog涉及主從複制,影響的是從庫??

也有說用binlog來作為備份恢複,恢複後的結果可能會跟主庫不一樣

執行一條更新語句時,此時當機了,有兩種情況:

  1. redolog刷盤成功,但binlog沒有 主庫由于有redolog的存在,能夠恢複,而binlog中并沒有相關的更新語句,導緻從庫中丢失了本次更新
  2. binlog刷盤成功,但redolog沒有 從庫由于有binlog的存在,記錄了更新,binlog 會被複制到從庫,從庫執行了這條更新語句,而主庫的redolog還沒刷盤成功,導緻崩潰後沒法恢複,主庫丢失了本次更新

XA事務、兩階段送出的具體流程

第一階段

這裡是事務管理器,相當于一個總的管理者,其負責管理參與事務的多個部分【比如這裡的redolog,binlog】,放到分布式系統裡邊,可能是兩個不同的系統

應用程式調用了事務管理器的送出方法,此後第一階段分為兩個步驟:

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

事務管理器通知參與該事務的各個資料總管,通知他們開始準備事務。

資料總管接收到消息後開始準備階段,寫好事務日志并執行事務,但不送出,然後将是否就緒的消息傳回給事務管理器(此時已經将事務的大部分事情做完,以後的内容耗時極小)。

第二階段

第二階段也分為兩個步驟:

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

事務管理器在接受各個消息後,開始分析,如果有任意其一失敗,則發送復原指令,否則發送送出指令。 各個資料總管接收到指令後,執行(耗時很少),并将送出消息傳回給事務管理器。

内部XA事務

内部XA事務主要指單節點執行個體内部,一個事務跨多個存儲引擎進行讀寫,那麼就會産生内部XA事務

存儲引擎與插件之間,存儲引擎與存儲引擎之間

送出過程

将 redo log 的寫入拆成了兩個步驟:prepare 和 commit,中間再穿插寫入binlog,具體如下:

  • prepare 階段:将 XID(内部 XA 事務的 ID) 寫入到 redo log,同時将 redo log 對應的事務狀态設定為 prepare,然後将 redo log 重新整理到硬碟; commit 階段:把 XID 寫入到 binlog,然後将 binlog 重新整理到磁盤,接着調用引擎的送出事務接口,将 調用引擎的送出事務接口,将 redo log 狀态設定為 commit(将事務設定為 commit 狀态後,刷入到磁盤 redo log 檔案,是以 commit 狀态也是會刷盤的);

出現異常會怎麼樣?

總結

隻要binlog也好了,就不需要復原,binlog沒好,就需要復原

其實就是兩個變成一個事務,redolog和binlog

寫入binlog出現異常

使用兩階段送出後,寫入binlog時發生異常也不會有影響,因為MySQL根據redo log日志恢複資料時,發現redo log還處于prepare階段,并且沒有對應binlog日志【binlog沒有目前内部XA事務的XID】,就會復原該事務。

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

設定commit異常

MySQL日志、事務原理:undolog、redolog、binlog、兩階段送出

并不會復原事務,它會執行上圖框住的邏輯,雖然redo log是處于prepare階段,但是能通過事務id找到對應的binlog日志【binlog有目前内部XA事務的XID】,是以MySQL認為是完整的,就會送出事務恢複資料。

疑問--好問題

在執行更新語句過程,會記錄redo log與binlog兩塊日志,以基本的事務為機關,redo log在事務執行過程中可以不斷寫入,而binlog隻有在送出事務時才寫入,是以redo log與binlog的寫入時機不一樣。

  • 事務執行中間過程的 redo log 也是直接寫在 redo log buffer 中的,這些緩存在 redo log buffer 裡的 redo log 也會被「背景線程」每隔一秒一起持久化到磁盤。

也就是說,事務沒送出的時候,redo log 也是可能被持久化到磁盤的。

那事務執行過程中不斷寫入,如果執行事務期間mysql當機呢? 此時重新開機後,mysql要利用redolog恢複,有一部分操作redolog已經寫入磁盤了,後續恢複會不會出現資料不一緻的情況?【因為事務實際上還沒有送出呢,是需要復原的相當于出現了異常】

此時由于有兩階段送出,binlog必須在事務送出後才寫入,是以此時即使redolog寫入了,但binlog還未寫入,重新開機時讀取到該redolog會發現沒有對應的binlog,會放棄掉

手撕面答環節 -- 這是一條分割線

MySQL 日志檔案有哪些?分别介紹下作用?

分别有 undolog、redolog、 binlog。

  1. undolog用于實作原子性,記錄版本鍊用于復原,并且屬于邏輯日志,記錄操作便于恢複
  2. redolog用于當機後的資料恢複,記錄實際的資料,屬于實體日志
  3. binlog主要用于主從複制,全量備份,相比redolog可存儲的内容更多,既可以實體也可以邏輯存儲,一般使用Mix

其中server層還有其他的日志,比如:

  • 錯誤日志(error log):錯誤日志檔案對 MySQL 的啟動、運作、關閉過程進行了記錄,能幫助定位 MySQL 問題。
  • 慢查詢日志(slow query log):慢查詢日志是用來記錄執行時間超過 long_query_time 這個變量定義的時長的查詢語句。通過慢查詢日志,可以查找出哪些查詢語句的執行效率很低,以便進行優化。
  • 一般查詢日志(general log):一般查詢日志記錄了所有對 MySQL 資料庫請求的資訊,無論請求是否正确執行。

redolog如何刷入磁盤的

有三種政策,參數不同的時候,對應的刷入時機不同 首先都會先寫到redolog的 buffer 區

參數為0:不寫入,等待背景線程定時【每秒】将 buffer 區中的内容寫入磁盤

若崩潰,會丢失上一秒中,buffer區裡邊所有的内容

參數為1:每次送出就寫入,先寫到 buffer 區,然後立馬調用 fsync 寫入磁盤

不會丢失,可以看成事務送出和寫入磁盤是一個原子性的操作?

參數為2:每次送出,先寫到 buffer 區,然後寫入到 pageCache 裡邊,由作業系統來決定何時寫入磁盤

隻有作業系統崩潰了,才會丢失上一秒中

要選擇哪種政策呢?

考慮性能:0>2>1 考慮安全性:1>2>0 綜合:2

為何要引入redolog來記錄呢?

将磁盤的随機IO轉換為追加寫入

原本如果我們每次都去更改磁盤裡邊的内容,則需要先随機IO找到,然後更改 有了寫入的話,我們每次都先追加寫入到redolog,然後再一次性去磁盤裡邊找就好了,而不是每次都去磁盤找

redolog 和 binlog 有什麼差別

存儲的容量不同

redolog以日志檔案組作為存儲的資料結構,容量是有限的,寫滿的時候會删除 undolog以追加檔案的形式存儲,理論上存儲容量是無限的

刷盤時機不同

redolog有三種不同的刷盤政策

标答:binlog隻在事務送出的時候才寫入,而redolog由于有背景程序,事務期間也會寫入

所屬的範疇不同

redolog是 innoDB 特有的,而binlog是 server 層的

用途不同

redolog用于當機後的資料恢複 binlog主要用于主從複制,全量備份

為什麼需要兩階段送出

  1. redolog送出成功,則binlog送出失敗了

此時主庫沒有影響,但從庫由于binlog丢失了,從庫得不到複制

  1. binlog送出成功,但redolog送出失敗了

從庫由于有binlog的存在,記錄了更新,binlog 會被複制到從庫,從庫執行了這條更新語句,而主庫的redolog還沒刷盤成功,導緻崩潰後沒法恢複,主庫丢失了本次更新

兩階段送出過程中出現異常了怎麼辦?

寫入binlog的時候出異常

此時redolog雖然準備好了,但是binlog還沒寫入,也就還沒關聯好binlog,當機恢複後掃描到這個redolog的時候發現其沒有關聯的binlog,認為是不完整的,會丢棄掉

commit階段出異常

由于此時redolog其實已經準備好了,并且binlog也已經寫入了,關聯好了XA事務的ID,也可以認定為這個事務是完整的了,當機恢複後,能找到該redolog對應的binlog

redolog事務執行期間也寫入,會不會有資料不一緻的問題?

事務執行期間也會寫入redolog,而binlog是事務送出後才寫入,那這樣當機恢複後會不會出現不一緻的情況呢?

  • 其實是不會的,因為事務執行過程中當機的話,binlog還沒寫呢,也就對應了我們上文的:寫binlog出現異常的情況,當機後恢複,掃描的時候會丢棄
原文連結:https://juejin.cn/post/7159853619477479461

繼續閱讀