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(日志邏輯序列號)的比較,里面並不簡簡單單就是我列舉的那樣,因為篇幅有限,這里就沒做具體的詳解了,只列舉了一個大概的比較過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM