MySQL三大日誌(binlog,redolog,undolog)詳解

轉發https://segmentfault.com/a/1190000041758784

一、MySQL日誌

MySQL日誌主要包括錯誤日誌、查詢日誌、慢查詢日誌、事務日誌、二進制日誌幾大類。其中比較重要的就是二進制日誌binlog(歸檔日誌)、事務日誌redo log(重做日誌)和undo log(回滾日誌)。

日誌關係如下圖:

二、redo log

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

MySQL實例掛了或者宕機了,重啓的時候InnoDB存儲引擎會使用rede log日誌恢復數據,保證事務的持久性和完整性。如下圖:

MySQL中數據是以頁爲單數存儲,當你查詢一條記錄時,硬盤會把一整頁的數據加載出來,加載出來的數據叫做數據頁,會放到Buffer Pool中。後續的查詢都是先從Buffer Pool中找,沒有找到再去硬盤加載其他的數據頁直到命中,這樣子可以減少磁盤IO的次數,提高性能。更新數據的時候也是一樣,優先去Buffer Pool中找

,如果存在需要更新的數據就直接更新。然後會把“在某個數據頁做了什麼修改”記錄到重做日誌緩存(redo log buffer)裏,在刷盤的時候會寫入redo log日誌文件裏。

如下圖:

小貼士:每條redo記錄由“表空間號+數據頁號+偏移量+修改數據長度+具體修改的數據”組成。

刷盤時機

理想情況下,事務一提交就會進行刷盤操作,但是實際上是刷盤的時機是根據策略來決定的。

InnoDB存儲引擎爲redo log的刷盤策略提供了innodb_flush_log_at_trx_commit參數,它支持三種策略:

  • 0:設置爲0的時候,每次提交事務時不刷盤。
  • 1:設置爲1的時候,每次提交事務時刷盤。
  • 2:設置爲2的時候,每次提交事務時都只把redo log buffer寫入page cache

innodb_flush_log_at_trx_commit參數默認爲1,當事務提交的時候會調用fsyncredo log進行刷盤,將redo log buffer寫入redo log文件中。

另外,Innodb存儲引擎有一個後臺線程,每隔1秒,就會把會redo log buffer中的內容寫入到文件系統緩存page cache,然後調用fsync刷盤。

如上圖,所以說一個沒有提交事務的redo log記錄,也會被刷盤。

下面是各種刷盤策略的流程圖。

innodb_flush_log_at_trx_commit = 0

如上圖,如果宕機了或者MySQL掛了可能造成1秒內的數據丟失。

innodb_flush_log_at_trx_commit = 1

如上圖,只要事務提交成功,redo log記錄就一定在磁盤裏,不會有任務數據丟失。

如果執行事務的時候MySQL掛了或者宕機了,這部分日誌丟失了,但是因爲事務沒有提交,所以日誌丟了也不會有損失。

innodb_flush_log_at_trx_commit = 2

如上圖,當事務提交成功時,redo log buffer日誌會被寫入page cache,然後後臺線程會刷盤寫入redo log,由於後臺線程是1秒執行一次所以宕機或者MySQL掛了可能造成1秒內的數據丟失。

日誌文件組

硬盤上存儲的redo log日誌文件不止一個,而是一個日誌文件組的形式出現的,每個的redo log文件大小都是一樣的。它採用的是環形數組形式,從頭開始寫,寫到末尾回到頭循環寫,如下圖所示:

在日誌文件組中有兩個重要的屬性,分別是witre pos、checkpoint

  • wirte pos:是當前記錄的位置,一邊寫一邊後移。
  • checkpoint:是當前要擦除的位置,也是後臺推移。

每次刷盤redo log記錄到日誌文件組中,wirte log位置就會後移更新。

每次MySQL加載日誌文件組恢復數據時,會清空加載過的redo log,並把checkpoint後移更新。

write pos 和 checkpoint 之間的還空着的部分可以用來寫入新的 redo log 記錄。

如果 witre pos追上checkpoint,表示日誌文件組滿了,這時候不能再寫入新的redo log記錄,MySQL得停下來,清空一些記錄,把checkpoint推薦一下。

redo log小結

redo log的作用和它的刷盤時機、存儲形式。

可以思考一個問題:只要每次把修改後的數據頁直接刷盤不就好了,爲什麼還要用redo log刷盤?不都是刷盤嗎?有什麼區別?

實際上,數據頁大小是16KB,刷盤比較耗時,可能就修改了數據頁的幾byte數據,沒有必要把整頁的數據刷盤。而且數據頁刷盤都是隨機寫,因爲一個數據頁對應的位置可能是在硬盤文件的隨機位置,所以性能很差。

如果是寫redo log,一行記錄就佔了幾十byte,只要包含了表空間號、數據頁號、磁盤文件偏移量、修改值,再加上是順序寫,所以刷盤效率很高。

所以用 redo log 形式記錄修改內容,性能會遠遠超過刷數據頁的方式,這也讓數據庫的併發能力更強。

三、binlog

redo log是物理日誌,記錄的是“在某個數據頁做了什麼修改”,屬於Innodb存儲引擎。

binlog日誌是邏輯日誌,記錄內容是語句的原始邏輯,屬於MySQL Server層。所有的存儲引擎只要發生了數據更新,都會產生binlog日誌。

binlog日誌的作用

可以說MySQL數據庫的數據備份、主備、主主、住從都離不開binlog,需要依賴binlog來同步數據,保證數據一致性。

binlog會記錄所有涉及更新數據的邏輯規則,並且按順序寫。

記錄格式

binlog日誌有三種格式,可以通過binlog_format參數設置,有以下三種:

  • statement
  • row
  • mixed

設置statement記錄的內容是SQL語句原文,比如執行一條update T set update_time = now() where id = 1,記錄內容如下:

同步數據時,會執行記錄的SQL語句,但是有個問題update_time = now()這裏會獲取到當前系統問題,直接執行會導致與原庫數據不一致。

爲了解決這種問題,我們需要將binlog_format設置成row,記錄的不再是簡單的SQL語句了,還包含了操作的具體數據,記錄內容如下:

row格式記錄的內容看不到詳細信息,通過mysqlbinlog工具解析出來。

update_time = now()變成了具體的時間,條件後面的@1、@2都是該行數據第1個~2個字段的原始值(假設這張表只有2個字段)。

設置成row帶來的好處就是同步數據的一致性,通常情況都設置成row,這樣可以爲數據庫的恢復與同步帶來更好的可靠性。但是這種格式需要大量的容量來記錄,比較佔用空間,恢復與同步時會更消耗IO資源,影響執行速度。

所以又有了一種折中方案,設置爲mixed,記錄的內容是前兩者的混合。

MySQL會判斷這條SQL語句是否會引起數據不一致,如果是就用row格式,否則就用statement格式。

寫入機制

binlog的寫入時機爲事務執行過程中,先把日誌寫到binlog cache,事務提交的時候再把binlog cache寫到binlog文件中(實際先會寫入page cache,然後再由fsync寫入binlog文件)。

因爲一個事務的binlog不能被拆開,無論這個事務多大,也要確保一次性寫入,所以系統會給每個線程分配一塊內存作爲binlog cache。可以通過binlog_cache_size參數控制單線程binlog_cache大小,如果存儲內容超過了這個參數,就要暫存到磁盤。

binlog日誌刷盤流程如下:

  • 上圖的write,是指把日誌寫入到文件系統的page cache,並沒有把數據持久化硬盤,所以速度比較快。
  • 上圖的 fsync纔是將數據庫持久化到硬盤的操作。

writefsync的時機可以由參數sync_binlog控制,可以配置成0、1、N(N>1)

  • 設置成0時:表示每次提交事務都只會write,由系統自行判斷什麼時候執行fsync
  • 設置成1時:表示每次提交事務都會執行fsync,就和redo log日誌刷盤流程一樣。
  • 設置成N時:表示每次提交事務都會write,但是積累N個事務後才fsync

設置爲0時如下圖:

從上圖可知,sync_bilog = 0設置成0,只把日誌寫入page cache雖然性能得到了提高,但是事務提交了fsync的時候宕機了,可能造成binlog日誌的丟失。

設置爲2時如下圖:

在出現IO瓶頸的場景裏,將sync_binlog設置成一個比較大的值,可以提升性能。

同樣的,如果機器宕機,會丟失最近N個事務的binlog日誌。

兩階段提交

redo log(重做日誌)讓InnoDB存儲引擎有了崩潰恢復的能力。

binlog(歸檔日誌)保證了MySQL集羣架構數據的一致性。

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

在執行更新語句過程,會記錄redo logbinlog兩塊日誌,以基本的事務爲單位,redo log在事務執行過程中可以不斷寫入,而binlog日誌只有在提交事務的時候纔會寫入,所以它們寫入的時機不一樣。

思考一個問題,如果redo log和binlog兩份日誌之間的邏輯不一樣,會出現什麼問題呢?MySQL是怎麼解決這個問題的呢?

比如有這樣一個場景,假設有這麼一條語句update T set c = 1 where id = 2(c原值爲0),假如執行過程中寫完redo log日誌後,在寫入binlog的時候發生了異常,會出現什麼情況呢?

如下圖:

由於binlog日誌沒寫完就異常,這個時候binlog日誌裏面沒有對應的修改記錄,之後使用binlog同步的數據的時候就會少這一次的更新,這一行數據c = 0,而原庫使用redo log日誌恢復,這一行數據c = 1 ,最終數據不一致。如下圖:

爲了解決兩份日誌之間的邏輯不一致的問題,InnoDB存儲引擎使用兩階段提交方案。

redo log日誌的寫入拆分成兩個步驟preparecommit,如下圖:

使用兩階段提交後,寫入binlog時發生異常也沒關係,因爲MySQL根據redo log日誌恢復數據時,發現redo log日誌處於prepare階段,並且沒有對應binlog日誌(根據事務id對應),所以就會回滾事務。

再想一個場景,redo lgo設置commit階段發生異常,事務會不會回滾呢?

並不會回滾事務,雖然redo log是處於prepare階段,但是存在對應的事務binlog日誌,所以MySQL認爲是完整的,所以不會回滾事務。

undo log

想要保證事務的原子性,就需要在發生異常時,對已經執行的操作進行回滾,在MySQL中恢復機制是通過undo log(回滾日誌)實現的,所有事務進行的修改都會先被記錄到這個回滾日誌,然後再執行其他相關的操作。如果執行過程中遇到異常的話,我們直接利用回滾日誌中的信息將數據回滾到修改之前的樣子。並且,回滾日誌會先於數據持久化到磁盤上。這樣就保證了即使遇到數據庫突然宕機等情況,當用戶再次啓動數據庫的時候,數據庫還能夠通過查詢回滾日誌來回滾將之前未完成的事務。

另外,MVCC的實現依賴:隱藏字段、Read Viewundo log。在底層實現中,InnoDB通過數據行的DB_TRX_IDRead View來判斷數據的可見性,如不可見,則通過數據行DB_ROLL_PTR找到undo log中的歷史版本。每個事務讀到的數據版本可能是不一樣的,在同一個事物裏,用戶只能看到該事務創建Read View之前已經提交的修改和該事務本身做的修改。

總結

MySQL InnoDB引擎使用redo log日誌保證事務的持久性,使用undo log日誌保證事務的原子性。

MySQL數據庫的數據備份、主備、主主、主從離不開binlog,需要依賴binlog來同步數據,保證數據的一致性。

 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章