MYSQL三大日誌-binlog、redo log、undo log
前言
我們都清楚日誌是mysql的一個重要組成部分,記錄著資料庫執行期間各種狀態資訊。而Mysql日誌又分為錯誤日誌、查詢日誌、慢查詢日誌、二進位制日誌(binlog)和事務日誌(redo log、undo log)。其中在我們開發中聊的比較多的就是二進位制日誌(binlog)和事務日誌(redo log、undo log)。其實慢查詢日誌也是我們開發中比較常見的日誌,常用於sql優化。本文主要介紹binlog、redo log、undo log三種日誌
專業名詞知識
首先,我們先來了解一下mysql中的專業名詞,看到一篇InfoQ的文章介紹的還不錯,那麼直接引入文章的截圖:
這些名詞的話在後面的文章中可能會出現,這裡先做一個瞭解。
BinLog(二進位制日誌)
二進位制日誌:binary log。簡稱binLog。記錄了對Mysql資料庫執行更改的所有操作,但是不包括SELECT 和 SHOW這類操作。以二進位制的形式儲存在磁碟中。是屬於Mysql Server層記錄,任何儲存引擎的 mysql 資料庫都會記錄binlog日誌。並且binlog 還是 mysql 的邏輯日誌
邏輯日誌:可以簡單理解為記錄的就是sql語句
物理日誌:mysql 資料最終是儲存在資料頁的,物理日誌記錄的就是資料頁變更
binlog 是通過追加的方式進行寫入的,可以通過max_binlog_size引數設定每個binlog檔案的大小,當檔案大小達到max_binlog_size後,會生成新的二進位制日誌檔案來儲存。從Mysql5.0後預設值1073741824(1G)
binlog使用場景
binlog 的主要使用場景有兩個,分別是 主從複製 和 資料恢復
主從複製:在 Master 端開啟 binlog,然後將 binlog 推送到各個 Slave 從端,Slave 端重放 binlog 從而達到主從資料一致
資料恢復:通過使用 mysqlbinlog 工具來恢復資料
binlog 記錄過程及刷盤時機
binlog 大致記錄過程是將所有未提交(uncommitted)的二進位制日誌寫入到 binlog buffer中,等該事務提交(committed)時,然後通過刷盤時機,控制刷入 OS Buffer,控制 fsync() 進行寫入 Binlog File 日記檔案磁碟的過程。
對於 binlog, MYSQL 是通過引數 sync_binlog 引數來控制刷盤時機,取值範圍是 0-N:
- 0: 不去強制要求,由系統自行判斷何時寫入磁碟
- 1: 每次事務提交(committed)的時候都要將 binlog 寫入磁碟
- N: 每提交 N 個事務,才會將 binlog 寫入磁碟
可以看出當 sync_binlog = 1時,資料是最安全的。這也是MySQL 5.7.7之後的版本的預設值。但是這樣的話可以會犧牲一定的效能來保證資料的一致性。
binlog 日誌格式
binlog 日誌有三種格式,分別為 STATMENT、ROW 和 MIXED
在MYSQL 5.7.7 之前,預設的格式是 STATMENT,MySQL 5.7.7 之後,預設值是 ROW, 日誌格式可以通過binlog-format 指定
- STATMENT:基於SQL語句的複製(statement-based replication, SBR),每一條會修改資料的sql語句會記錄到binlog 中
- 優點:不需要記錄每一行的變化,減少了binlog 日誌量,節約了 IO,從而提高了效能
- 缺點:在某些情況下會導致主從資料不一致,比如執行sysdate()等
- ROW:基於行的複製(row-based replication, RBR),不記錄每條sql語句的上下文,記錄哪條資料被修改了
- 優點:能夠解決特定情況下的儲存過程、或function,或trigger的呼叫和觸發無法被正確複製的問題
- 缺點:會產生大量的日誌,尤其是'alter table'的時候會讓日誌暴漲
- MIXED:基於STATMENT 和 ROW 兩種模式的混合複製(mixed-based replication, MBR),預設採用 STATMENT 格式進行二進位制日誌檔案的記錄,但是在一些情況下會使用ROW格式,可能的情況有:
- 1): 表的儲存引擎為NDB,這時對錶的 DML 操作都會以 ROW 格式記錄
- 2): 使用了 UUID()、USER()、 CURRENT_USER()、FOUND_ROWS()、ROW_COUNT()等不確定函式
- 3): 使用了INSERT DELAY 語句
- 4): 使用了使用者定義函式 (UDF)
- 5): 使用了臨時表(temporary table)
redo log(重做日誌)
我們都知道,事務有一個特性叫做 永續性。也就是事務提交成功,那麼對資料庫做的修改就被永久儲存下來,不可能因為任何原因再回到原來的狀態。
那麼,mysql是如何保證一致性的呢?
最簡單的辦法就是每次事務提交成功,將該事務涉及修改的資料頁全部重新整理到磁碟。但是這麼做會有嚴重的效能問題,主要體現在下面兩個方面:
- 因為 InnoDB 是以 頁 為單位進行磁碟互動的,而一個事務很可能只修改一個數據頁裡面的幾個位元組,這個時候如果將完整的資料頁全部重新整理到磁碟的話,太浪費資源了
- 一個事務可能涉及修改多個數據頁,並且這些資料頁在物理上並不連續,使用隨機IO寫入效能太差
因此 mysql 設計了 redo log, 具體來說就是隻記錄事務對資料頁做了哪些修改,這樣就能完美地解決效能問題了(相對而言檔案更小並且是順序IO)。
Redo Log概念
Redo log 是重做日誌,屬於 InnoDB儲存引擎的日誌。是物理日誌,日誌記錄的內容是資料頁的更改,這個頁"做了什麼改動"。如:add xx記錄 to Page1,向資料頁Page1增加一個記錄。
redo log 包括兩部分:一個是記憶體中的日誌緩衝(redo log buffer),其是易失的。二是重做日誌檔案(redo log file),其是持久的。
Redo Log作用
- 前滾操作:具備 crash-safe 能力,提供斷電重啟時解決事務丟失資料問題
- 提高效能:mysql 每執行一條 DML 語句,先將記錄寫入 redo log buffer。當等到有空閒執行緒、記憶體不足、Redo Log滿了時刷髒。寫Redo log 是順序寫入,刷髒是隨機寫,節省的是隨機寫磁碟的 IO 消耗 (轉成順序寫),所以效能得到提升。此技術成為WAL技術:Write-Ahead Logging,它的關鍵點就是先寫日誌磁碟,再寫資料磁碟。具體來說,當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log裡面,並更新記憶體,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤裡面,而這個更新往往是在系統比較空閒的時候做
Redo log兩階段提交
mysql為了保證 Binlog 和 Redo log 資料的一致性,採用了兩階段提交
可以看到將 redo log 的寫入拆成了兩個步驟:prepare 和 commit兩個階段
為什麼需要採用"兩階段提交"呢?
這裡假設不採用"兩階段提交"的話,寫入完 redo log,接著寫binlog時,這時候資料庫崩潰,這時候 binlog 裡面就沒有記錄這個語句。但是 redo log裡面會儲存這條 c的值是1的記錄,資料庫崩潰我們也是可以通過redo log的日誌將資料恢復過來。如果我們需要備份日誌的時候,存起來的 binlog 沒有這條語句,需要用這個 binlog 來恢復臨時庫的話,由於這個語句的 binlog 丟失,這個臨時庫就會少了這一次更新,恢復出來的這一行 c 的值就是 0,與原庫的值不同。所以我們需要保證redo log 跟 binlog 資料一致性
Redo log 容災恢復過程
- 判斷 redo log 是否完整,如果判斷是完整(commit)的,直接用 Redo log恢復
- 如果 redo log 只是預提交 prepare 但不是 commit 狀態,這個時候會去判斷 binlog 是否完整,如果完整就提交 Redo log,用 Redo log 恢復,不完整就回滾事務,丟棄資料。
只有在 redo log 狀態為 prepare 時,才會去檢查 binlog 是否存在,否則只校驗 redo log 是否是 commit 就可以啦。怎麼檢查 binlog:一個完整事務 binlog 結尾有固定的格式。
Redo log 刷盤時機
在計算機作業系統中,使用者空間(User Space)下的緩衝區資料一般情況下是無法直接寫入磁碟的,中間必須經過作業系統核心空間( kernel space )緩衝區( OS buffer)
因此,redo log每次先寫入 redo log buffer中,然後通過刷盤時機,將 redo log buffer 的資料寫入 redo log file,寫入 redo log file 實際上是先寫入 OS Buffer,然後再通過系統呼叫 fsync() 將其刷到 redo log file中,大致過程:
mysql 通過引數 innodb_flush_log_at_trx_commit 來控制刷盤時機,取值是0、1和2三種值。
- 0(延遲寫): 事務提交時並不會立即將 redo log buffer 中日誌寫入到 os buffer中,而是每秒寫入 os buffer 並呼叫 fsync() 寫入到 redo log file 中。也就是說設定為0時是(大約)每秒重新整理寫入到磁碟中的,當系統崩潰,會丟失1秒鐘的資料
- 1(實時寫,實時刷):事務每次提交都會將 redo log buffer 中的日誌寫入 os buffer 並呼叫 fsync() 刷到 redo log file 中。這種方式即使系統崩潰也不會丟失任何資料,是最安全的,同時也是預設值
- 2(實時寫,延遲刷):每次提交都僅寫入到 os buffer,然後是每秒呼叫 fsync() 將 os buffer 中的日誌寫入到 redo log file
Redo log 儲存方式
前面說過,redo log 實際上記錄資料頁的變更,而這種變更記錄是沒必要一直儲存,因此 redo log 實現上採用了大小固定,迴圈寫入的方式,當寫到結尾時,會回到開頭迴圈寫日誌。如下圖:
上圖是日誌磁碟的 Redo log 環形設計圖(從頭開始寫,寫到結束又從頭開始寫~迴圈)。write pos 和 check point 是兩個指標,write pos 表示 redo log 當前記錄的 LSN(日誌邏輯序列號(佔用8位元組))位置,check point 表示資料頁更改記錄刷盤後對應 redo log 所處的 LSN(日誌邏輯序列號)位置。
write pos 到 check point 之間的部分(圖中綠色的部分),用於記錄新的記錄; check point 到 write pos 之間是 redo log 待落盤的資料頁更改記錄。每次寫入,write pos 指標會順時針推進,當 write pos追上check point 時,會先推動 check point 向前移動,空出位置再記錄新的日誌
Redo log 容災恢復過程
啟動 innodb 的時候,不管上次是正常關閉還是異常關閉,總是會進行恢復操作。因為 redo log 記錄的是資料頁的物理變化,因此恢復的時候速度比邏輯日誌(如 binlog )要快很多。
重啟 innodb 時,首先會檢查磁碟中資料頁的 LSN,如果資料頁的 LSN 小於 redo log 日誌中的 LSN,則會從 checkpoint 開始恢復。
還有一種情況,在宕機前正處於 checkpoint 的刷盤過程,且資料頁的刷盤進度超過了日誌頁的刷盤進度,此時會出現資料頁中記錄的 LSN 大於 redo log 日誌中的 LSN,這時超出日誌進度的部分將不會被重做,因為這本身就表示已經做過的事情,無需再重做
提問1: 為啥 Binlog 沒有 crash-safe 功能?
redo log 和 binlog 有一個很大的區別就是,一個是迴圈寫,一個是追加寫。也就是說 redo log 只會記錄未刷盤的日誌,已經刷入磁碟的資料都會從 redo log 這個有限大小的日誌檔案裡刪除。binlog 是追加日誌,儲存的是全量日誌。
當資料庫 crash 後,想要恢復未刷盤但已經寫入 redo log 和 binlog 的資料,binlog 是無法恢復的。雖然 binlog 擁有全量的日誌,但沒有一個標誌讓 innodb 判斷哪些資料已經刷盤,哪些資料還沒有
舉個例子, binlog 記錄了兩條日誌:
1. 給 ID = 2 這一行的 c 欄位加1
2. 給 ID = 2 這一行的 c 欄位加2
在記錄1刷盤後,記錄2未刷盤時,資料庫 crash。重啟後,只通過 binlog 資料庫無法判斷這兩條記錄哪條已經寫入磁碟,哪條沒有寫入磁碟,不管是兩條都恢復至記憶體,還是都不恢復,對 ID=2 這行資料來說,都不對
但 redo log 不一樣,只要刷入磁碟的資料,都會從 redo log 中抹掉,資料庫重啟後,直接把 redo log 中的資料都恢復至記憶體就可以了。這就是為什麼 redo log 具有 crash-safe 的能力,而 binlog 不具備
提問2: 保證 crash-safe 為啥要用兩個日記,不能用一個日記嗎(Redo log 或 Binglog)?
針對問題1我們知道只有 binlog 日誌,沒有 redo log 是不能做到故障恢復的。那麼針對只有 redo log日誌,沒有 binlog 日誌,這也是不行的,因為 redo log 是 innodb 持有的,且日誌上的記錄落盤後會被抹掉。因此需要 binlog 和 redo log 兩者同時記錄,才能保證當資料庫發生宕機重啟時,資料不會丟失。
undo log(回滾日誌)
我們都知道事務四大特性中有一個是原子性,具體來說就是 原子性是指對資料庫的一系列操作,要麼全部成功,要麼全部失敗,不可能出現部分成功的情況
實際上,原子性 底層就是通過 undo log 實現的。undo log 主要記錄了資料的邏輯變化,比如一條INSERT 語句,對應著有一條的DELETE的undo log,對於每個UPDATE 語句,對應一條相反的 UPDATE 的 undo log,這樣在發生錯誤時,就能回滾到事務之前的資料狀態
undo log 跟 redo log 是屬於innodb 引擎的日誌
undo log 作用
- 回滾資料:當資料發生異常錯誤時,根據執行 undo log 就可以回滾到事務之前的資料狀態,保證原子性,要麼全部成功,要麼全部失敗
- MVCC 一致性檢視:通過 undo log 找到對應的資料版本號,是保證 MVCC 檢視的一致性的必要條件
undo log 記錄過程及刷盤時機
undo log 的記錄過程的話跟 redo log 過程差不多,都是先記錄到 Log Buffer 中,然後通過刷盤時機將 buffer 中的日誌 重新整理到 undo log file 中。但是對於 undo log 沒找到對應的刷盤引數設計
總結
前面主要介紹了 binlog、redo log、undo log三種日誌。binlog是屬於mysql Server層的,屬於整個mysql的,而redo log、undo log是屬於innodb儲存引擎獨有的,redo log、undo log是事務日誌,binlog是二進位制日誌負責記錄對mysql資料庫有修改的sql操作。其中還有難懂的點就是redo log進行容災恢復的過程LSN(日誌邏輯序列號)的比較,裡面並不簡簡單單就是我列舉的那樣,因為篇幅有限,這裡就沒做具體的詳解了,只列舉了一個大概的比較過程。