mysql兩個重要的日志redolog和binlog


一.redo log

  • 使用原因

    在 MySQL 里有這個問題,如果每一次的更新操作都需要寫進磁盤,然后磁盤也要找到對應的那條記錄,然后再更新,整個過程 IO 成本、查找成本都很高
    
    其實就是 MySQL 里經常說到的 WAL 技術,WAL 的全稱 是 Write-Ahead Logging,它的關鍵點就是先寫日志,再寫磁盤
    
    當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log里面,並更新內存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤里面,而這個更新往往是在系統比較空閑的時候做
    
  • 原理

    InnoDB 的 redo log 是固定大小的,比如可以配置為一組 4 個文件,每個文 件的大小是 1GB,那么這塊“粉板”總共就可以記錄 4GB 的操作。從頭開始寫,寫到末 尾就又回到開頭循環寫,如上面這個圖所示。
    
    write pos 是當前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件 開頭。checkpoint 是當前要擦除的位置,也是往后推移並且循環的,擦除記錄前要把記錄 更新到數據文件。
    
    write pos 和 checkpoint 之間的是“粉板”上還空着的部分,可以用來記錄新的操作。如 果 write pos 追上 checkpoint,表示“粉板”滿了,這時候不能再執行新的更新,得停下 來先擦掉一些記錄,把 checkpoint 推進一下。
    
    有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟 失,這個能力稱為crash-safe。
    

二.binlog(歸檔日志)

  • 與redo log的區別

    1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎 都可以使用。
    
    2. redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”,雖然沒有保存整頁數據,但是可以記錄頁面級別的變更。;binlog 是邏輯日 志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
    
    3. redo log 是循環寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個,並不會覆蓋以前的日志。
    

三.兩段提交

更新過程

  • update T set c=c+1 where ID=2;
  1. 執行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則,需要先從磁 盤讀入內存,然后再返回。
  2. 執行器拿到引擎給的行數據,把這個值加上 1,比如原來是 N,現在就是 N+1,得到 新的一行數據,再調用引擎接口寫入這行新數據。
  3. 引擎將這行新數據更新到內存中,同時將這個更新操作記錄到 redo log 里面,此時 redo log 處於 prepare 狀態(處於prepare狀態之后要寫入磁盤中,但是redo log的commit得標識為沒有commit)。然后告知執行器執行完成了,隨時可以提交事務。
  4. 執行器生成這個操作的 binlog,並把 binlog 寫入磁盤。
  5. 執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成。
  • 淺色框表示是在 InnoDB 內部執行的, 深色框表示是在執行器中執行的。

  • 必要性

    如果不使用“兩階段提交”,那么數據庫的狀態就有可能和用它的日志恢復出來的庫的狀態不一致。
    
  • 兩段提交發生時機

    我們單獨寫一個update語句的時候,就默認提交事務的,我們的兩階段,是發生在“提交階段”的
    如果是有begin...commit的語句序列,在執行“commit”這個語句的時候發生的提交階段,兩階段也就在此時發生
    
  • 在兩階段提交的不同時刻,MySQL 異常重啟會出現什么現象。

    • 如果在圖中時刻 A 的地方,也就是寫入 redo log 處於 prepare 階段之后、寫 binlog 之 前,發生了崩潰(crash),由於此時 binlog 還沒寫,redo log 也還沒提交,所以崩潰恢復的時候,這個事務會回滾。這時候,binlog 還沒寫,所以也不會傳到備庫。

    • 在時刻 B,也就是 binlog 寫完,redo log 還沒 commit 前發生 crash,那崩潰恢復的時候 MySQL 會怎么處理 ?

      1. 如果 redo log 里面的事務是完整的,也就是已經有了 commit 標識,則直接提交;
      2. 如果 redo log 里面的事務只有完整的 prepare,則判斷對應的事務 binlog 是否存在並完整:
      		a. 如果是,則提交事務; 
      		b. 否則,回滾事務。
      		
      協助理解:
      當時刻B發生crash,重啟后這部分redo log都丟失了,那么何談判斷redo log是否有完整的prepare還是commit標志呢?
      答: 寫binlog之前,就已經都寫了盤並且fsync同步到磁盤了
      
    • 時刻 B 發生 crash 對應的就是 2(a) 的情況,崩潰恢復過程中事務會被提交。

四.binlog寫入機制

其實,binlog 的寫入邏輯比較簡單:事務執行過程中,先把日志寫到 binlog cache,事 務提交的時候,再把 binlog cache 寫到 binlog 文件中。

一個事務的 binlog 是不能被拆開的,因此不論這個事務多大,也要確保一次性寫入。這 就涉及到了 binlog cache 的保存問題。

系統給 binlog cache 分配了一片內存,每個線程一個,參數 binlog_cache_size 用於控 制單個線程內 binlog cache 所占內存的大小。如果超過了這個參數規定的大小,就要暫 存到磁盤。

事務提交的時候,執行器把 binlog cache 里的完整事務寫入到 binlog 中,並清空 binlog cache。狀態如圖 1 所示。

可以看到,每個線程有自己 binlog cache,但是共用同一份 binlog 文件。

圖中的 write,指的就是指把日志寫入到文件系統的 page cache,並沒有把數據持久化 到磁盤,所以速度比較快。此時write步驟,寫入操作系統維護的內存中,此內存是磁盤中文件系統申請的內存;

圖中的 fsync,才是將數據持久化到磁盤的操作。一般情況下,我們認為 fsync 才占磁 盤的 IOPS。

write 和 fsync 的時機,是由參數 sync_binlog 控制的:

  • sync_binlog=0 的時候,表示每次提交事務都只 write,不 fsync;
  • sync_binlog=1 的時候,表示每次提交事務都會執行 fsync;
  • sync_binlog=N(N>1) 的時候,表示每次提交事務都 write,但累積 N 個事務后才fsync。

因此,在出現 IO 瓶頸的場景里,將 sync_binlog 設置成一個比較大的值,可以提升性 能。在實際的業務場景中,考慮到丟失日志量的可控性,一般不建議將這個參數設成 0, 比較常見的是將其設置為 100~1000 中的某個數值。

但是,將 sync_binlog 設置為 N,對應的風險是:如果主機發生異常重啟,會丟失最近 N 個事務的 binlog 日志。

五.redo log寫入機制

redo log buffer 里面的內容,是不是每次生成后都要直接持久化到磁 盤呢?

答案是,不需要。 如果事務執行期間 MySQL 發生異常重啟,那這部分日志就丟了。由於事務並沒有提交, 所以這時日志丟了也不會有損失。

那么,另外一個問題是,事務還沒提交的時候,redo log buffer 中的部分日志有沒有可能 被持久化到磁盤呢?

答案是,確實會有, 下面解釋。

1.寫入狀態

這個問題,要從 redo log 可能存在的三種狀態說起。這三種狀態,對應的就是圖 2 中的 三個顏色塊。

這三種狀態分別是:

  1. 存在redo log buffer中,物理上是存在mysql進程內存中,就是圖中紅色部分
  2. 寫到磁盤(write),但是沒有持久化(fsync),物理上是在文件系統的page cache里面,也就是圖中得黃色部分
  3. 持久化到磁盤,對應的是hard disk,也就是圖中得綠色部分

日志寫到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盤 的速度就慢多了。

2.寫入策略

為了控制 redo log 的寫入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 參 數,它有三種可能取值:

  1. 設置為 0 的時候,表示每次事務提交時都只是把 redo log 留在 redo log buffer 中 ;
  2. 設置為 1 的時候,表示每次事務提交時都將 redo log 直接持久化到磁盤;
  3. 設置為 2 的時候,表示每次事務提交時都只是把 redo log 寫到 page cache。

InnoDB 有一個后台線程,每隔 1 秒,就會把 redo log buffer 中的日志,調用 write 寫 到文件系統的 page cache,然后調用 fsync 持久化到磁盤。

注意,事務執行中間過程的 redo log 也是直接寫在 redo log buffer 中的,這些 redo log 也會被后台線程一起持久化到磁盤。也就是說,一個沒有提交的事務的 redo log,也 是可能已經持久化到磁盤的。

實際上,除了后台線程每秒一次的輪詢操作外,還有兩種場景會讓一個沒有提交的事務的 redo log 寫入到磁盤中。

  1. 一種是,redo log buffer 占用的空間即將達到 innodb_log_buffer_size 一半的時 候,后台線程會主動寫盤。注意,由於這個事務並沒有提交,所以這個寫盤動作只是 write,而沒有調用 fsync,也就是只留在了文件系統的 page cache。
  2. 另一種是,並行的事務提交的時候,順帶將這個事務的 redo log buffer 持久化到磁 盤。假設一個事務 A 執行到一半,已經寫了一些 redo log 到 buffer 中,這時候有另 外一個線程的事務 B 提交,如果 innodb_flush_log_at_trx_commit 設置的是 1,那么 按照這個參數的邏輯,事務 B 要把 redo log buffer 里的日志全部持久化到磁盤。這時 候,就會帶上事務 A 在 redo log buffer 里的日志一起持久化到磁盤。

3.兩階段提交詳解

這里需要說明的是,我們介紹兩階段提交的時候說過,時序上 redo log 先 prepare, 再 寫 binlog,最后再把 redo log commit。

如果把 innodb_flush_log_at_trx_commit 設置成 1,那么 redo log 在 prepare 階段就 要持久化一次,因為有一個崩潰恢復邏輯是要依賴於 prepare 的 redo log,再加上 binlog 來恢復的。

每秒一次后台輪詢刷盤,再加上崩潰恢復這個邏輯,InnoDB 就認為 redo log 在 commit 的時候就不需要 fsync 了,只會 write 到文件系統的 page cache 中就夠了。

通常我們說 MySQL 的“雙 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都設置成 1。也就是說,一個事務完整提交前,需要等待兩次刷盤,一次是 redo log(prepare 階段),一次是 binlog。

在“雙 1”配置下,兩階段提交的詳細流程

  1. redo log的prepare write階段,寫入到page cache 里(redo log prepare)
  2. 然后binlog進入write階段,寫入page cache里(binlog)
  3. 然后redo log 的prepare階段,進行持久化fsync操作,持久化到磁盤(redo log prepare)
  4. binlog進行持久化階段fsync階段,持久化到磁盤(binlog)
  5. commit階段,先將binlog添加commit標識,再將redo log添加commit標識,redo log進行write,寫入page cache(commit)

六.組提交

1.redo log組提交

這里,我需要先和你介紹日志邏輯序列號(log sequence number,LSN)的概念。LSN 是單調遞增的,用來對應 redo log 的一個個寫入點。每次寫入長度為 length 的 redo log, LSN 的值就會加上 length。

LSN 也會寫到 InnoDB 的數據頁中,來確保數據頁不會被多次執行重復的 redo log。關 於 LSN 和 redo log、checkpoint 的關系,我會在后面的文章中詳細展開。

如圖 3 所示,是三個並發事務 (trx1, trx2, trx3) 在 prepare 階段,都寫完 redo log buffer,持久化到磁盤的過程,對應的 LSN 分別是 50、120 和 160。

從圖中可以看到,

  1. trx1 是第一個到達的,會被選為這組的 leader;
  2. 等 trx1 要開始寫盤的時候,這個組里面已經有了三個事務,這時候 LSN 也變成了160;
  3. trx1 去寫盤的時候,帶的就是 LSN=160,因此等 trx1 返回時,所有 LSN 小於等於160 的 redo log,都已經被持久化到磁盤;
  4. 這時候 trx2 和 trx3 就可以直接返回了。

所以,一次組提交里面,組員越多,節約磁盤 IOPS 的效果越好。但如果只有單線程壓 測,那就只能老老實實地一個事務對應一次持久化操作了。

在並發更新場景下,第一個事務寫完 redo log buffer 以后,接下來這個 fsync 越晚調 用,組員可能越多,節約 IOPS 的效果就越好。

2.binlog組提交

為了讓一次 fsync 帶的組員更多,MySQL 有一個很有趣的優化:拖時間。在介紹兩階段 提交的時候,我曾經給你畫了一個圖,現在我把它截過來。

圖中,我把“寫 binlog”當成一個動作。但實際上,寫 binlog 是分成兩步的:

  1. 先把 binlog 從 binlog cache 中寫到磁盤上的 binlog 文件,此時write步驟,寫入操作系統維護的內存中,此內存是磁盤中文件系統申請的內存;
  2. 調用 fsync 持久化。

MySQL 為了讓組提交的效果更好,把 redo log 做 fsync 的時間拖到了步驟 1 之后。也 就是說,上面的圖變成了這樣:

這么一來,binlog 也可以組提交了。在執行圖 5 中第 4 步把 binlog fsync 到磁盤時,如 果有多個事務的 binlog 已經寫完了,也是一起持久化的,這樣也可以減少 IOPS 的消耗。

不過通常情況下第 3 步執行得會很快,所以 binlog 的 write 和 fsync 間的間隔時間短, 導致能集合到一起持久化的 binlog 比較少,因此 binlog 的組提交的效果通常不如 redo log 的效果那么好。

如果你想提升 binlog 組提交的效果,可以通過設置 binlog_group_commit_sync_delay 和binlog_group_commit_sync_no_delay_count 來實現。

  1. binlog_group_commit_sync_delay 參數,表示延遲多少微秒后才調用 fsync;

  2. binlog_group_commit_sync_no_delay_count 參數,表示累積多少次以后才調用fsync。

這兩個條件是或的關系,也就是說只要有一個滿足條件就會調用 fsync。

所以,當 binlog_group_commit_sync_delay 設置為 0 的時候, binlog_group_commit_sync_no_delay_count 也無效了。

之前有同學在評論區問到,WAL 機制是減少磁盤寫,可是每次提交事務都要寫 redo log 和 binlog,這磁盤讀寫次數也沒變少呀?

現在你就能理解了,WAL 機制主要得益於兩個方面:

  1. redo log 和 binlog 都是順序寫,磁盤的順序寫比隨機寫速度要快;
  2. 組提交機制,可以大幅度降低磁盤的 IOPS 消耗。

七.mysql提升IO性能

可以考慮以下三種方法:

  1. 設置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 參數,減少 binlog 的寫盤次數。這個 方法是基於“額外的故意等待”來實現的,因此可能會增加語句的響應時間,但沒有丟失數據的風險。
  2. 將 sync_binlog 設置為大於 1 的值(比較常見是 100~1000)。這樣做的風險是,主 機掉電時會丟 binlog日志。
  3. 將 innodb_flush_log_at_trx_commit 設置為 2。這樣做的風險是,主機掉電的時候會丟數據。

我不建議你把 innodb_flush_log_at_trx_commit 設置成 0。因為把這個參數設置成 0, 表示 redo log 只保存在沒有生氣啦申請的內存中,這樣的話 MySQL 本身異常重啟也會丟數據,風險太 大。而 redo log 寫到文件系統的 page cache 的速度也是很快的,所以將這個參數設置 成 2 跟設置成 0 其實性能差不多,但這樣做 MySQL 異常重啟時就不會丟數據了(因為數據已經寫入了磁盤中文件系統申請的內存中是操作系統維護的內存,而不是在mysql申請的內存中),相比之下風險會更小。

站在巨人的肩膀上摘蘋果:

https://time.geekbang.org/column/intro/100020801


免責聲明!

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



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