天天看點

MySQL三大日志(redo log、bin log、undo log)

作者:代碼派

日志是MySQL資料庫的重要組成部分,記錄着資料庫運作期間各種狀态資訊,下面這篇文章主要給大家介紹了關于MySQL三大日志(binlog、redo log和undo log)的相關資料,需要的朋友可以參考下

1. redo log

redo log概述

redo log(重做日志)是InnoDB存儲引擎獨有的,它讓MySQL擁有了崩潰恢複能力。

比如 MySQL 執行個體挂了或當機了,重新開機時,InnoDB存儲引擎會使用redo log恢複資料,保證資料的持久性與完整性。

MySQL三大日志(redo log、bin log、undo log)

MySQL 中資料是以頁為機關,你查詢一條記錄,會從硬碟把一頁的資料加載出來,加載出來的資料叫資料頁,會放入到 Buffer Pool 中。

後續的查詢都是先從 Buffer Pool 中找,沒有命中再去硬碟加載,減少硬碟 IO 開銷,提升性能。

更新表資料的時候,也是如此,發現 Buffer Pool 裡存在要更新的資料,就直接在 Buffer Pool 裡更新。

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

MySQL三大日志(redo log、bin log、undo log)

刷盤時機

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

  1. 設定為 0 的時候,表示每次事務送出時不進行刷盤操作;
  2. 設定為 1 的時候,表示每次事務送出時都将進行刷盤操作 *預設值);
  3. 設定為 2 的時候,表示每次事務送出時都隻把 redo log buffer 内容寫入 page cache。

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

MySQL三大日志(redo log、bin log、undo log)

也就是說,一個沒有送出事務的 redo log 記錄,也可能會刷盤

除了背景線程每秒1次的輪詢操作,還有一種情況,當 redo log buffer 占用的空間即将達到 innodb_log_buffer_size 一半的時候,背景線程會主動刷盤。

innodb_flush_log_at_trx_commit=0

MySQL三大日志(redo log、bin log、undo log)

為0時,如果MySQL挂了或當機可能會有1秒資料的丢失。

innodb_flush_log_at_trx_commit=2
           
MySQL三大日志(redo log、bin log、undo log)

為1時, 隻要事務送出成功,redo log記錄就一定在硬碟裡,不會有任何資料丢失。

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

innodb_flush_log_at_trx_commit=2

MySQL三大日志(redo log、bin log、undo log)

為2時, 隻要事務送出成功,redo log buffer中的内容隻寫入檔案系統緩存(page cache)。

如果僅僅隻是MySQL挂了不會有任何資料丢失,但是當機可能會有1秒資料的丢失。

日志檔案組

硬碟上存儲的 redo log 日志檔案不隻一個,而是以一個日志檔案組的形式出現的,每個的redo日志檔案大小都是一樣的。

它采用的是環形數組形式,從頭開始寫,寫到末尾又回到頭循環寫,如下圖所示。

MySQL三大日志(redo log、bin log、undo log)

2.binlog binlog

概述

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

而 binlog 是邏輯日志,記錄内容是語句的原始邏輯,類似于“給 ID=2 這一行的 c 字段加 1”,屬于MySQL Server 層。

不管用什麼存儲引擎,隻要發生了表資料更新,都會産生 binlog 日志。

可以說MySQL資料庫的資料備份、主備、主主、主從都離不開binlog,需要依靠binlog來同步資料,保證資料一緻性。

MySQL三大日志(redo log、bin log、undo log)

binlog會記錄所有涉及更新資料的邏輯操作,并且是順序寫。

記錄格式

binlog 日志有三種格式,可以通過binlog_format參數指定。

*tatement* *ow* *ixed*

指定statement,記錄的内容是SQL語句原文,比如執行一條update T set update_time=now where id=1,記錄的内容如下。

MySQL三大日志(redo log、bin log、undo log)

同步資料時,會執行記錄的SQL語句,但是有個問題,update_time=now這裡會擷取目前系統時間,直接執行會導緻與原庫的資料不一緻。

為了解決這種問題,我們需要指定為row,記錄的内容不再是簡單的SQL語句了,還包含操作的具體資料,記錄内容如下。

MySQL三大日志(redo log、bin log、undo log)

這樣就能保證同步資料的一緻性,通常情況下都是指定為row,這樣可以為資料庫的恢複與同步帶來更好的可靠性。

但是這種格式,需要更大的容量來記錄,比較占用空間,恢複與同步時會更消耗IO資源,影響執行速度。

是以就有了一種折中的方案,指定為mixed,記錄的内容是前兩者的混合。

MySQL會判斷這條SQL語句是否可能引起資料不一緻,如果是,就用row格式,否則就用statement格式。

寫入機制

binlog的寫入時機也非常簡單,事務執行過程中,先把日志寫到binlog cache,事務送出的時候,再把binlog cache寫到binlog檔案中。

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

我們可以通過binlog_cache_size參數控制單個線程 binlog cache 大小,如果存儲内容超過了這個參數,就要暫存到磁盤(Swap)。

binlog日志刷盤流程如下:

MySQL三大日志(redo log、bin log、undo log)

上圖的 write,是指把日志寫入到檔案系統的 page cache,并沒有把資料持久化到磁盤,是以速度比較快

上圖的 fsync,才是将資料持久化到磁盤的操作

刷盤時機

write和fsync的時機,可以由參數sync_binlog控制,預設是0。

為0的時候,表示每次送出事務都隻write,由系統自行判斷什麼時候執行fsync。

MySQL三大日志(redo log、bin log、undo log)

雖然性能得到提升,但是機器當機,page cache裡面的 binlog 會丢失。

為了安全起見,可以設定為1,表示每次送出事務都會執行fsync,就如同 redo log 日志刷盤流程 一樣。

最後還有一種折中方式,可以設定為N(N>1),表示每次送出事務都write,但累積N個事務後才fsync。

MySQL三大日志(redo log、bin log、undo log)

在出現IO瓶頸的場景裡,将sync_binlog設定成一個比較大的值,可以提升性能。同樣的,如果機器當機,會丢失最近N個事務的binlog日志。

3.兩階段送出

redo log(重做日志)讓InnoDB存儲引擎擁有了崩潰恢複能力。

binlog(歸檔日志)保證了MySQL叢集架構的資料一緻性。

雖然它們都屬于持久化的保證,但是側重點不同。

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

MySQL三大日志(redo log、bin log、undo log)

回到正題,redo log與binlog兩份日志之間的邏輯不一緻,會出現什麼問題?

我們以update語句為例,假設id=2的記錄,字段c值是0,把字段c值更新成1,SQL語句為update T set c=1 where id=2。

假設執行過程中寫完redo log日志後,binlog日志寫期間發生了異常,會出現什麼情況呢?

MySQL三大日志(redo log、bin log、undo log)

由于binlog沒寫完就異常,這時候binlog裡面沒有對應的修改記錄。是以,之後用binlog日志恢複資料時,就會少這一次更新,恢複出來的這一行c值是0,而原庫因為redo log日志恢複,這一行c值是1,最終資料不一緻。

MySQL三大日志(redo log、bin log、undo log)

為了解決兩份日志之間的邏輯一緻問題,InnoDB存儲引擎使用兩階段送出方案。

原理很簡單,将redo log的寫入拆成了兩個步驟prepare和commit,這就是兩階段送出。

MySQL三大日志(redo log、bin log、undo log)

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

MySQL三大日志(redo log、bin log、undo log)

再看一個場景,redo log設定commit階段發生異常,那會不會復原事務呢?

MySQL三大日志(redo log、bin log、undo log)

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

4.undo log

我們知道如果想要保證事務的原子性,就需要在異常發生時,對已經執行的操作進行復原,在 MySQL 中,恢複機制是通過 復原日志(undo log) 實作的,所有事務進行的修改都會先記錄到這個復原日志中,然後再執行相關的操作。如果執行過程中遇到異常的話,我們直接利用 復原日志 中的資訊将資料復原到修改之前的樣子即可!并且,復原日志會先于資料持久化到磁盤上。這樣就保證了即使遇到資料庫突然當機等情況,當使用者再次啟動資料庫的時候,資料庫還能夠通過查詢復原日志來復原将之前未完成的事務。

另外,MVCC 的實作依賴于:隐藏字段、Read View、undo log。在内部實作中,InnoDB 通過資料行的 DB_TRX_ID 和 Read View 來判斷資料的可見性,如不可見,則通過資料行的 DB_ROLL_PTR 找到 undo log 中的曆史版本。每個事務讀到的資料版本可能是不一樣的,在同一個事務中,使用者隻能看到該事務建立 Read View 之前已經送出的修改和該事務本身做的修改

5.小結

MySQL InnoDB 引擎使用 redo log(重做日志) 保證事務的持久性,使用 undo log(復原日志) 來保證事務的原子性。

MySQL資料庫的資料備份、主備、主主、主從都離不開binlog,需要依靠binlog來同步資料,保證資料一緻性。

繼續閱讀