MySQL之 bin log、redo log和undo log 簡介
日誌是MySQL
資料庫的重要組成部分,記錄著資料庫執行期間各種狀態資訊。MySQL
中日誌型別有很多種,但對於開發來說,最常見和最重要的就是binlog
、redolog
和undolog
。本篇文章主要對這三種日誌型別做一個簡要的介紹。
前置知識
- 邏輯日誌:可以簡單得理解為
sql
語句; - 物理日誌:
MySQL
中資料都是儲存在資料頁中的,物理日誌記錄的是資料頁上的變更;
binlog
binlog
是MySQL Server
層記錄的日誌,也就是說,不管MySQL
使用的什麼儲存引擎,都會有bin log
產生。binlog
是MySQL
中最重要的日誌,它記錄了所有的DDL
和DML
(除了查詢語句)語句,即所有修改資料的操作,以二進位制的形式儲存在磁碟中,binlog
binlog 作用
- 主從複製:在
Mater
端開啟binlog
,然後將binlog
傳送到各個Slave
端,Slave
端重放binlog
從而達到主從資料一致; - 資料恢復:基於時間點,可以通過
mysqlbinlog
工具來恢復資料;
binlog 主從複製原理
MySQL
主從同步主要依靠binlog
來實現。這裡簡單介紹一下基本原理。
-
主節點 binlog dump 執行緒
當從節點連線主節點時,主節點會建立一個log dump
執行緒,用於傳送binlog
的內容。在讀取binlog
中的操作時,此執行緒會對主節點上的binlog
加鎖,當讀取完成,甚至在發動給從節點之前,鎖會被釋放; -
從節點I/O執行緒
當從節點上執行start slave
命令之後,從節點會建立一個I/O執行緒用來連線主節點,請求主庫中更新的binlog
。I/O
執行緒接收到主節點binlog dump
程序發來的更新之後,儲存在本地relaylog
中; -
從節點SQL執行緒
SQL
執行緒負責讀取relaylog
中的內容,解析成具體的操作並執行,最終保證主從資料的一致性;
binlog的內容
上面說了,binlog
是一種邏輯日誌,可以簡單得理解為sql
語句,但是實際上還包含著執行的sql
語句的反向邏輯。delete
對應著delete
本身以及反向的insert
資訊;update
包含著對應的update
執行前後資料行的相關資訊;insert
insert
以及對應的delete
資訊。
binlog的格式
binlog
共有三種格式,分別是statement
、row
以及mixed
。MySQL 5.7.7
版本之前預設使用的是statement
,MySQL 5.7.7
之後預設使用的是row
。日誌的格式可以通過my.ini
配置檔案中的binlog-format
來修改。
statement
:基於sql
語句的複製(statement-based replication,SBR
),每一條修改資料的sql
語句都會記錄到binlog
中。- 優點:不需要具體記錄某一行的變化,節約空間,減少
io
,提高效能; - 缺點:在執行
sysdate()
或者sleep()
等操作的時候,可能導致主從資料不一致的情況; row
:基於行記錄的複製(row-based replication,RBR
),不記錄sql
語句上下文相關資訊,而是記錄哪條記錄被修改的細節。- 優點:非常詳細地記錄每一行記錄修改的細節,因而不會出現資料無法被正確複製的情況;
- 缺點:由於會非常詳細地記錄每一條記錄修改的細節,這樣會產生大量的日誌內容。假設現在有一條
update
語句,修改了很多條記錄,則每條修改記錄都會記錄到binlog
中。特別地,alter table
這個操作,由於表結構的變化,每行記錄都會發生變化,導致日誌量暴增;
mixed
:根據上面所說的,statement
和row
各有優缺點,因此出現了mixed
這個版本,將這二者進行混合。一般情況下使用statement
格式來進行儲存,當遇到statement
無法解決時,切換為row
格式來進行儲存。
特別地,上面說了,新版本(MySQL 5.7.7
之後)預設使用的row
格式,這裡的row
也做了相應的優化,在遇到alter table
這個操作時採用statement
格式進行記錄,其餘操作仍然使用row
格式。
binlog的刷盤時機
對於InnoDB
儲存引擎來說,只有在事務提交的時候才會記錄binlog
,此時記錄還在記憶體中,MySQL
通過sync_binlog
來控制binlog
的刷盤時機,取值範圍為0-N
:
- 0:不強制刷到磁碟,由系統自行判斷何時寫入磁碟中;
- 1:每次提交後都要將
binlog
寫入磁碟中; N
:每N
個事務,才會將binlog
寫入磁碟中;
binlog的物理檔案大小
在my.ini
配置檔案中,可以通過max_binlog_size
來配置binlog
的大小。當日志量超過binlog
檔案的大小時,系統會重新生成一個新的檔案來繼續儲存檔案。當一個事務比較大時,
當日志越來越多的時候,此時佔據的物理空間太大怎麼辦?MySQL
提供了一種自動刪除的機制,還是在my.ini
配置檔案中,可以通過配置expire_logs_days
這個引數來解決,單位為天。當這個引數為0,表示永不刪除;為N
時,表示第N
天后自動刪除。
redolog
redolog
是InnoDB
引擎專有的日誌系統。主要是用來實現事務的永續性以及實現crash-safe
功能。redolog
屬於物理日誌,記錄的是sql
語句執行之後資料頁上的具體修改內容。
redolog的作用
我們都知道,當MySQL
執行的時候,會將資料從磁碟中載入到記憶體當中。當執行sql
語句對資料進行修改時,修改後的內容其實都只是暫時儲存到記憶體當中,如果此時斷電或者出現其他情況,這些修改就會丟失。因而,當修改完資料之後,MySQL
會尋找機會將這些記憶體中的記錄刷回到磁碟當中。但這就出現一個性能問題,主要有兩個方面:
InnoDB
中是以頁為資料單位與磁碟進行互動的,而一個事務很可能只是修改了一個頁上的幾個位元組,如果將一個完整的資料頁刷回磁碟當中,浪費資源;- 一個事務可能涉及到多個數據頁,這些資料頁只是邏輯上連續,在物理上並不連續,使用隨機
IO
效能太差;
因此,MySQL
設計了redolog
來記錄事務對資料頁具體做了哪些修改,之後將redolog
再刷回磁碟當中。你可能會有疑惑,本來就是想減少io
,這不又加上一次io
麼?InnoDB
的設計者在設計之初就已經考慮到了這些。redolog
檔案一般都比較小,且在刷回磁碟的過程中是順序io
,相比於隨機io
來說,效能更好。
redolog簡介
redolog
由兩部分組成,一個是記憶體中的日誌快取redo log buffer
,一個是磁碟中的日誌檔案redo log file
。當每次對資料記錄進行修改的時候,都會將這些修改內容先寫入redo log buffer
中,後續等待合適的時機將記憶體中的修改刷回到redo log file
中。這種先寫日誌,再寫磁碟的技術就是WAL(Write-Ahead Logging)
技術。需要注意的是redolog
比資料頁先刷回磁碟,聚簇索引,二級索引,undo
頁面的修改,均需要記錄redolog
。
redolog的整體流程
如圖所示,當對資料記錄進行修改時,redolog
的流程如下:
- 若資料已在記憶體中則直接進行修改,否則先將資料從磁碟載入到記憶體中;
- 修改完成之後,生成一條
redolog
,將這條redolog
寫入redo log buffer
中,記錄的是修改之後的值; - 根據選定的策略,將
redo log file
中的內容刷回到redo log file
中;
redolog刷回redo log file的策略
在計算機作業系統中,使用者空間的資料一般無法直接寫入到磁碟中,中間必須先經過作業系統核心空間緩衝區。因此,redo log buffer
寫入redo log file
實際上是先寫入os buffer
中,再通過系統呼叫fsync()
刷回到磁碟中,過程如下:
在my.ini
配置檔案中,可以通過innodb_flush_log_at_trx_commit
引數來配置redo log buffer
如何刷回redo log file
的策略。
- 0:事務提交後不會將
redo log buffer
中的日誌寫入到os buffer
,而是每秒將redo log buffer
寫入到os buffer
中,再呼叫fsync()
寫入到redo log file
中。當系統崩潰時,會丟失1秒鐘的資料; - 1:事務提交後都會將
redo log buffer
中的日誌寫入os buffer
,再呼叫fsync
刷到redo log file
中。這樣方式即使系統崩潰也不會丟失任何資料,但由於每次事務提交時都要寫入磁碟,效能較差; - 2:事務提交後僅僅將
redo log buffer
中的日誌寫入os buffer
,然後每秒呼叫fsync()
將os buffer
中的日誌寫入到redo log file
;
示意圖如下所示:
redo log 格式
redolog
採用固定大小,迴圈寫入的格式,當redolog
寫滿之後,會重新從頭開始寫。為什麼這麼設計呢?
redo log存在的意義主要就是降低對資料頁刷盤的要求。redolog
記錄了資料頁上的修改,但是當資料頁也刷回到磁碟後,這些記錄就失去作用了。因此當MySQL
判斷之前的redolog
已經失去作用之後,新資料會將這些失效的資料進行覆蓋。那如何判斷該不該進行覆蓋呢?
上圖是redo log file
的示意圖,write pos
表示redolog
當前記錄的日誌序列號LSN(log sequence number)
。當資料頁也已經刷回磁碟之後,會更新redo log file
中的LSN
,表示到這個LSN
之前的資料已經落盤,這個LSN
就是check point
。write pos
到check point
之間的部分是redolog
空餘的部分,用於記錄新的記錄;check point
到write pos
之間是redolog
已經記錄的資料頁修改部分,但此時資料頁還未刷回磁碟的部分。當write pos
追上check point
時,會先推動check point
向前移動,空出位置再記錄新的日誌。
啟動innodb
的時候,不管上次是正常關閉還是異常關閉,總是會進行恢復操作。恢復時,會先檢查資料頁中的LSN
,如果這個LSN
小於redolog
中的LSN
,即write pos
位置,說明在redolog
上記錄著資料頁上尚未完成的操作,接著就會從最近的一個check point
出發,開始同步資料。
那有沒有可能資料頁中的LSN
大於redolog
中的LSN
呢?答案是當然可能。出現這種情況時,這時超出redolog
的部分將不會重做,因為這本身就表示已經做過的事情,無需再重做。
redolog與binlog區別
redolog | binlog | |
---|---|---|
檔案大小 | redo log 的大小是固定的。 |
binlog 可通過配置引數max_binlog_size 設定每個binlog 檔案的大小。 |
實現方式 | redo log 是InnoDB 引擎層實現的,並不是所有引擎都有。 |
binlog 是Server 層實現的,所有引擎都可以使用 binlog 日誌 |
記錄方式 | redo log 採用迴圈寫的方式記錄,當寫到結尾時,會回到開頭迴圈寫日誌。 | binlog 通過追加的方式記錄,當檔案大小大於給定值後,後續的日誌會記錄到新的檔案上 |
適用場景 | redo log 適用於崩潰恢復(crash-safe) |
binlog 適用於主從複製和資料恢復 |
由binlog
和redo log
的區別可知:binlog
日誌只用于歸檔,只依靠binlog
是沒有crash-safe
能力的。但只有redo log
也不行,因為redo log
是InnoDB
特有的,且日誌上的記錄落盤後會被覆蓋掉。因此需要binlog
和redo log
二者同時記錄,才能保證當資料庫發生宕機重啟時,資料不會丟失。
兩階段提交
上面簡單介紹了redolog
和binlog
,在對資料進行修改時,他們都會對這些修改進行儲存落地,只是一個是物理日誌,一個是邏輯日誌。那他倆具體在修改過程中是如何執行的呢?
假設現在有一條update
語句要執行,update from table_name set c=c+1 where id=2
,執行流程如下:
- 先定位到
id=2
這一條記錄; - 執行器拿到引擎給的行資料,把這個值加上 1,得到新的一行資料,再呼叫引擎介面寫入這行新資料;
- 引擎將這行新資料更新到記憶體中,同時將這個更新操作記錄到
redolog
裡面,此時redolog
處於prepare
狀態。然後告知執行器執行完成了,隨時可以提交事務; - 執行器生成這個操作的
binlog
,並把binlog
寫入磁碟; - 執行器呼叫引擎的提交事務介面,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成;
示意圖如下所示:
這種將redolog
的寫入拆分成prepare
和commit
兩個步驟的過程稱之為兩階段提交。
redolog
和binlog
都可以用於表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保持邏輯上的一致。如果不使用兩階段提交,而是先寫其中一個再寫另外一個可能會帶來一些問題。
此時還是使用update
來舉例。假設當前id=2
,有一個欄位c=0
,分別分析以下情況:
先寫redolog
再寫binlog
假設先寫redolog
,當redolog
寫完,但是binlog
還未寫完的時候,此時MySQL
突然出現異常導致重啟。由於之前redolog
已經寫完,系統重啟後,修改的記錄仍然存在,所以恢復後這一行 c 的值是 1。但由於系統重啟,binlog
中並未有這條記錄。之後備份日誌的時候,存起來的binlog
裡面就沒有這條語句。然後你會發現,如果需要用這個 binlog
來恢復臨時庫的話,由於這個語句的binlog
丟失,這個臨時庫就會少了這一次更新,恢復出來的這一行 c 的值就是 0,與原庫的值不同。
先寫binlog再寫redolog
假如先寫binlog
,然後寫redolog
的時候系統重啟。重啟之後,redolog
中沒有對c
進行修改的記錄,此時c
的值還是0。但是 binlog
裡面已經記錄了“把 c 從 0 改成 1”這個日誌。所以,在之後用 binlog
來恢復的時候就多了一個事務出來,恢復出來的這一行 c 的值就是 1,與原庫的值不同。
因此,綜上所述,如果是先寫某一個日誌再寫另一個日誌,就會出現數據庫的狀態與使用binlog
恢復出來的庫的狀態不一致的情況。
undolog
undolog
主要用來記錄某條行記錄被修改之前的狀態,記錄的是修改前的資料。這樣的話,當事務進行回滾時,就可以通過undolog
將記錄恢復到事務開始前的樣子。事務的原子性和永續性也是依靠undolog
來實現的。undo log
主要記錄了資料的邏輯變化,比如一條INSERT
語句,對應一條DELETE
的undo log
,對於每個UPDATE
語句,對應一條相反的UPDATE
的undo log
,這樣在發生錯誤時,就能回滾到事務之前的資料狀態。同時,在進行資料恢復的時候,與binlog
,redolog
結合使用,保證了資料恢復的正確性。
undolog
的作用流程如下所示:
- 在事務開始之前將修改前的版本寫入到
undo log
中; - 開始進行修改,將修改過的資料儲存到記憶體當中;
- 將
undolog
持久化到磁碟當中; - 將資料頁刷回到磁碟當中;
- 事務提交;
需要注意的是,與redolog
一樣,undolog
也是要先於資料頁刷回到磁碟當中。在恢復資料時,如果undolog
是完整的,可以根據undolog
來回滾事務。
在一個事務當中,可能會對同一條資料進行多次修改,那麼是不是每一次修改前的記錄都要記錄到undolog
中呢?這樣的話,會導致undolog
日誌量太大,此時redolog
就要上場了。在一個事務當中,如果是對同一條記錄進行修改,undolog
只會記錄事務開始前的原始記錄,當再次對這條記錄進行修改時,redolog
會記錄後續的變化。在資料恢復時,redolog
完成前滾,undolog
完成回滾,二者相互協調完成資料的恢復。過程如下所示:
還有一個功能就是MVCC
多版本控制鏈了,這個請參考這篇文章,[MVCC 多版本控制鏈]。
總結
binlog
,redolog
和undolog
是MySQL
中最重要的三個日誌,在進行資料恢復時,三者進行協調合作,保證資料恢復的正確性。
參考
詳細分析MySQL事務日誌(redo log和undo log)