前言
MySQL
日志 主要包括錯誤日志、查詢日志、慢查詢日志、事務日志、二進制日志幾大類。其中,比較重要的還要屬二進制日志 binlog
(歸檔日志)和事務日志 redo log
(重做日志)和 undo log
(回滾日志)。
今天就來聊聊 redo log
(重做日志)、binlog
(歸檔日志)、兩階段提交、undo log
(回滾日志)。
redo log
redo log
(重做日志)是InnoDB
存儲引擎獨有的,它讓MySQL
擁有了崩潰恢復能力。
比如 MySQL
實例掛了或宕機了,重啟時,InnoDB
存儲引擎會使用redo log
恢復數據,保證數據的持久性與完整性。

MySQL
中數據是以頁為單位,你查詢一條記錄,會從硬盤把一頁的數據加載出來,加載出來的數據叫數據頁,會放入到 Buffer Pool
中。
后續的查詢都是先從 Buffer Pool
中找,沒有命中再去硬盤加載,減少硬盤 IO
開銷,提升性能。
更新表數據的時候,也是如此,發現 Buffer Pool
里存在要更新的數據,就直接在 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 ,也就是說當事務提交時會調用 fsync
對 redo log 進行刷盤
另外,InnoDB
存儲引擎有一個后臺線程,每隔1
秒,就會把 redo log buffer
中的內容寫到文件系統緩存(page cache
),然后調用 fsync
刷盤。

也就是說,一個沒有提交事務的 redo log
記錄,也可能會刷盤。
為什么呢?
因為在事務執行過程 redo log
記錄是會寫入redo log buffer
中,這些 redo log
記錄會被后臺線程刷盤。

除了后臺線程每秒1
次的輪詢操作,還有一種情況,當 redo log buffer
占用的空間即將達到 innodb_log_buffer_size
一半的時候,后臺線程會主動刷盤。
下面是不同刷盤策略的流程圖。
innodb_flush_log_at_trx_commit=0

為0
時,如果MySQL
掛了或宕機可能會有1
秒數據的丟失。
innodb_flush_log_at_trx_commit=1

為1
時, 只要事務提交成功,redo log
記錄就一定在硬盤里,不會有任何數據丟失。
如果事務執行期間MySQL
掛了或宕機,這部分日志丟了,但是事務并沒有提交,所以日志丟了也不會有損失。
innodb_flush_log_at_trx_commit=2

為2
時, 只要事務提交成功,redo log buffer
中的內容只寫入文件系統緩存(page cache
)。
如果僅僅只是MySQL
掛了不會有任何數據丟失,但是宕機可能會有1
秒數據的丟失。
日志文件組
硬盤上存儲的 redo log
日志文件不只一個,而是以一個日志文件組的形式出現的,每個的redo
日志文件大小都是一樣的。
比如可以配置為一組4
個文件,每個文件的大小是 1GB
,整個 redo log
日志文件組可以記錄4G
的內容。
它采用的是環形數組形式,從頭開始寫,寫到末尾又回到頭循環寫,如下圖所示。

在個日志文件組中還有兩個重要的屬性,分別是 write pos、checkpoint
- write pos 是當前記錄的位置,一邊寫一邊后移
- checkpoint 是當前要擦除的位置,也是往后推移
每次刷盤 redo log
記錄到日志文件組中,write pos
位置就會后移更新。
每次 MySQL
加載日志文件組恢復數據時,會清空加載過的 redo log
記錄,并把 checkpoint
后移更新。
write pos
和 checkpoint
之間的還空著的部分可以用來寫入新的 redo log
記錄。

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

redo log 小結
相信大家都知道 redo log
的作用和它的刷盤時機、存儲形式。
現在我們來思考一個問題:只要每次把修改后的數據頁直接刷盤不就好了,還有 redo log
什么事?
它們不都是刷盤么?差別在哪里?
1Byte=8bit
1KB=1024Byte
1MB=1024KB
1GB=1024MB
1TB=1024GB
實際上,數據頁大小是16KB
,刷盤比較耗時,可能就修改了數據頁里的幾 Byte
數據,有必要把完整的數據頁刷盤嗎?
而且數據頁刷盤是隨機寫,因為一個數據頁對應的位置可能在硬盤文件的隨機位置,所以性能是很差。
如果是寫 redo log
,一行記錄可能就占幾十 Byte
,只包含表空間號、數據頁號、磁盤文件偏移量、更新值,再加上是順序寫,所以刷盤速度很快。
所以用 redo log
形式記錄修改內容,性能會遠遠超過刷數據頁的方式,這也讓數據庫的并發能力更強。
其實內存的數據頁在一定時機也會刷盤,我們把這稱為頁合并,講
Buffer Pool
的時候會對這塊細說
binlog
redo log
它是物理日志,記錄內容是“在某個數據頁上做了什么修改”,屬于 InnoDB
存儲引擎。
而 binlog
是邏輯日志,記錄內容是語句的原始邏輯,類似于“給 ID=2 這一行的 c 字段加 1”,屬于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()
這里會獲取當前系統時間,直接執行會導致與原庫的數據不一致。
為了解決這種問題,我們需要指定為row
,記錄的內容不再是簡單的SQL
語句了,還包含操作的具體數據,記錄內容如下。

row
格式記錄的內容看不到詳細信息,要通過mysqlbinlog
工具解析出來。
update_time=now()
變成了具體的時間update_time=1627112756247
,條件后面的@1、@2、@3 都是該行數據第 1 個~3 個字段的原始值(假設這張表只有 3 個字段)。
這樣就能保證同步數據的一致性,通常情況下都是指定為row
,這樣可以為數據庫的恢復與同步帶來更好的可靠性。
但是這種格式,需要更大的容量來記錄,比較占用空間,恢復與同步時會更消耗IO
資源,影響執行速度。
所以就有了一種折中的方案,指定為mixed
,記錄的內容是前兩者的混合。
MySQL
會判斷這條SQL
語句是否可能引起數據不一致,如果是,就用row
格式,否則就用statement
格式。
寫入機制
binlog
的寫入時機也非常簡單,事務執行過程中,先把日志寫到binlog cache
,事務提交的時候,再把binlog cache
寫到binlog
文件中。
因為一個事務的binlog
不能被拆開,無論這個事務多大,也要確保一次性寫入,所以系統會給每個線程分配一個塊內存作為binlog cache
。
我們可以通過binlog_cache_size
參數控制單個線程 binlog cache 大小,如果存儲內容超過了這個參數,就要暫存到磁盤(Swap
)。
binlog
日志刷盤流程如下

- 上圖的 write,是指把日志寫入到文件系統的 page cache,并沒有把數據持久化到磁盤,所以速度比較快
- 上圖的 fsync,才是將數據持久化到磁盤的操作
write
和fsync
的時機,可以由參數sync_binlog
控制,默認是0
。
為0
的時候,表示每次提交事務都只write
,由系統自行判斷什么時候執行fsync
。

雖然性能得到提升,但是機器宕機,page cache
里面的 binglog 會丟失。
為了安全起見,可以設置為1
,表示每次提交事務都會執行fsync
,就如同binlog 日志刷盤流程一樣。
最后還有一種折中方式,可以設置為N(N>1)
,表示每次提交事務都write
,但累積N
個事務后才fsync
。

在出現IO
瓶頸的場景里,將sync_binlog
設置成一個比較大的值,可以提升性能。
同樣的,如果機器宕機,會丟失最近N
個事務的binlog
日志。
兩階段提交
redo log
(重做日志)讓InnoDB
存儲引擎擁有了崩潰恢復能力。
binlog
(歸檔日志)保證了MySQL
集群架構的數據一致性。
雖然它們都屬于持久化的保證,但是則重點不同。
在執行更新語句過程,會記錄redo log
與binlog
兩塊日志,以基本的事務為單位,redo log
在事務執行過程中可以不斷寫入,而binlog
只有在提交事務時才寫入,所以redo log
與binlog
的寫入時機不一樣。

回到正題,redo log
與binlog
兩份日志之間的邏輯不一致,會出現什么問題?
我們以update
語句為例,假設id=2
的記錄,字段c
值是0
,把字段c
值更新成1
,SQL
語句為update T set c=1 where id=2
。
假設執行過程中寫完redo log
日志后,binlog
日志寫期間發生了異常,會出現什么情況呢?

由于binlog
沒寫完就異常,這時候binlog
里面沒有對應的修改記錄。因此,之后用binlog
日志恢復數據時,就會少這一次更新,恢復出來的這一行c
值是0
,而原庫因為redo log
日志恢復,這一行c
值是1
,最終數據不一致。

為了解決兩份日志之間的邏輯一致問題,InnoDB
存儲引擎使用兩階段提交方案。
原理很簡單,將redo log
的寫入拆成了兩個步驟prepare
和commit
,這就是兩階段提交。

使用兩階段提交后,寫入binlog
時發生異常也不會有影響,因為MySQL
根據redo log
日志恢復數據時,發現redo log
還處于prepare
階段,并且沒有對應binlog
日志,就會回滾該事務。

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

并不會回滾事務,它會執行上圖框住的邏輯,雖然redo log
是處于prepare
階段,但是能通過事務id
找到對應的binlog
日志,所以MySQL
認為是完整的,就會提交事務恢復數據。
undo log
數據庫事務四大特性中有一個是原子性,具體來說就是原子性是指對數據庫的一系列操作,要么全部成功,要么全部失敗,不可能出現部分成功的情況。
我們知道如果想要保證事務的原子性,就需要在異常發生時,對已經執行的操作進行回滾,在 MySQL 中,恢復機制是通過 回滾日志(undo log) 實現的,所有事務進行的修改都會先先記錄到這個回滾日志中,然后再執行相關的操作。
如果執行過程中遇到異常的話,我們直接利用 回滾日志 中的信息將數據回滾到修改之前的樣子即可!并且,回滾日志會先于數據持久化到磁盤上。這樣就保證了即使遇到數據庫突然宕機等情況,當用戶再次啟動數據庫的時候,數據庫還能夠通過查詢回滾日志來回滾將之前未完成的事務。
另外,MVCC
的實現依賴于:隱藏字段、Read View、undo log。在內部實現中,InnoDB
通過數據行的 DB_TRX_ID
和 Read View
來判斷數據的可見性,如不可見,則通過數據行的 DB_ROLL_PTR
找到 undo log
中的歷史版本。
每個事務讀到的數據版本可能是不一樣的,在同一個事務中,用戶只能看到該事務創建 Read View
之前已經提交的修改和該事務本身做的修改。
總結
MySQL InnoDB 引擎使用 redo log(重做日志) 保證事務的持久性,使用 undo log(回滾日志) 來保證事務的原子性。
MySQL
數據庫的數據備份、主備、主主、主從都離不開binlog
,需要依靠binlog
來同步數據,保證數據一致性。
審核編輯:湯梓紅
-
MySQL
+關注
關注
1文章
840瀏覽量
27301 -
日志
+關注
關注
0文章
140瀏覽量
10780 -
binlog
+關注
關注
0文章
7瀏覽量
1292
原文標題:大廠基本功 | MySQL 三大日志 ( binlog、redo log 和 undo log ) 的作用?
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
干貨分享:MySQL零基礎入門視頻教程!
0基礎學Mysql:mysql入門視頻教程!
確保數據零丟失!阿里云數據庫RDS for MySQL 三節點企業版正式商用
MySQL中的redo log是什么
關于Mysql的20道問題詳解

評論