MySQL 爲什麼需要 redo log?

@[toc] 今天想和大家聊一聊 MySQL 中的 redo log,其實最早我是想聊兩階段提交的,後來想想可能有小夥伴還不瞭解 binlog,所以就先整了一篇 binlog:

binlog 大家懂了之後,接下來還差個 redo log,redo log 大家也懂了,那麼再講兩階段提交相信小夥伴們就很容易懂了,咱們一步一步來。

1. 誰的 redo log

學習 redo log,我覺得首先要搞明白一個問題,就是是誰的 redo log?

我們知道,MySQL 架構整體上分爲兩層:Server 層和存儲引擎層,如下圖:

前面松哥文章+視頻跟大家聊的 binlog,是 MySQL 自己提供的 binlog,而 redo log 則不是 MySQL 提供的,而是存儲引擎 InnoDB 自己提供的。所以在 MySQL 中就存在兩類日誌 binlog 和 redo log,存在兩類日誌既有歷史原因(InnoDB 最早不是 MySQL 官方存儲引擎)也有技術原因,這個咱們以後再細聊。

先把這個問題搞清楚,後面很多地方就容易懂了。

2. buffer pool

在正式介紹 redo log 之前,還有一個 buffer pool 需要大家瞭解。

小夥伴們知道,InnoDB 引擎存儲數據的時候,是以頁爲單位的,每個數據頁的大小默認是 16KB,我們可以通過如下命令來查看頁的大小:

16384/1024=16

剛好是 16KB。

計算機在存儲數據的時候,最小存儲單元是扇區,一個扇區的大小是 512 字節,而文件系統(例如 XFS/EXT4)最小單元是塊,一個塊的大小是 4KB,也就是四個塊組成一個 InnoDB 中的頁。我們在 MySQL 中針對數據庫的增刪改查操作,都是操作數據頁,說白了,就是操作磁盤。

但是大家想想,如果每一次操作都操作磁盤,那麼就會產生海量的磁盤 IO 操作,如果是傳統的機械硬盤,還會涉及到很多隨機 IO 操作,效率低的令人髮指。這嚴重影響了 MySQL 的性能。

爲了解決這一問題,MySQL 引入了 buffer pool,也就是我們常說的緩衝池。

buffer pool 的主要作用就是緩存索引和表數據,以避免每一次操作都要進行磁盤 IO,通過 buffer pool 可以提高數據的訪問速度。

通過如下命令可以查看 buffer pool 的默認大小:

134217728/1024/1024=128

默認大小是 128MB,因爲松哥這裏的 MySQL 是安裝在 Docker 中,所以這個分配的小一些。一般來說,如果一個服務器只是運行了一個 MySQL 服務,我們可以設置 buffer pool 的大小爲服務器內存大小的 75%~80%。

3. change buffer

在正式介紹 redo log 之前,還有一個 change buffer 需要大家瞭解。

前面我們說的 buffer pool 雖然提高了訪問速度,但是增刪改的效率並沒有因此提升,當涉及到增刪改的時候,還是需要磁盤 IO,那麼效率一樣低的令人髮指。

爲了解決這個問題,MySQL 中引入了 change buffer。change buffer 以前並不叫這個名字,以前叫 insert buffer,即只針對 insert 操作有效,現在改名叫 change buffer 了,不僅僅針對 insert 有效,對 delete 和 update 操作也是有效的,change buffer 主要是對非唯一的索引有效,如果字段是唯一性索引,那麼更新的時候要去檢查唯一性,依然無法避免磁盤 IO。

change buffer 就是說,當我們需要更改數據庫中的數據的時候,我們把更改記錄到內存中,等到將來數據被讀取的時候,再將內存中的數據 merge 到 buffer pool 然後返回,此時 buffer pool 中的數據和磁盤中的數據就會有差異,有差異的數據我們稱之爲髒頁,在滿足條件的時候(redo log 寫滿了、內存寫滿了、其他空閒時候),InnoDB 會把髒頁刷新回磁盤。這種方式可以有效降低寫操作的磁盤 IO,提升數據庫的性能。

通過如下命令我們可以查看 change buffer 的大小以及哪些操作會涉及到 change buffer:

  • innodb_change_buffer_max_size:這個配置表示 change buffer 的大小佔整個緩衝池的比例,默認值是 25%,最大值是 50%
  • innodb_change_buffering:這個操作表示哪些寫操作會用到 change buffer,默認的 all 表示所有寫操作,我們也可以自己設置爲 none/inserts/deletes/changes/purges 等。

不過 change buffer 和 buffer pool 都涉及到內存操作,數據不能持久化,那麼,當存在髒頁的時候,MySQL 如果突然掛了,就有可能造成數據丟失(因爲內存中的數據還沒寫到磁盤上),但是我們在實際使用 MySQL 的時候,其實並不會有這個問題,那麼問題是怎麼解決的?那就得靠 redo log 了。

4. redo log 的誕生

在正式介紹 redo log 之前,還需要給大家普及一個概念:WAL。

WAL 全稱是 Write-Ahead Logging 中文譯作預寫日誌。啥意思呢?就是說 MySQL 的寫操作並不是立刻更新到磁盤上,而是先記錄在日誌上,然後在合適的時間再更新到磁盤上,這樣的好處是錯開高峯期的磁盤 IO,提高 MySQL 的性能。

配合上前面的 buffer pool 和 change buffer,WAL 就是說在操作 buffer pool 和 change buffer 之前,會先把記錄寫到 redo log 日誌中,然後再去更新 buffer pool 或者 change buffer,這樣,即使系統突然崩了,將來也可以通過 redo log 恢復數據。當然,redo log 本身又分爲:

  • 日誌緩衝(redo log buffer),該部分日誌是易失性的。
  • 重做日誌(redo log file),這是磁盤上的日誌文件,該部分日誌是持久的。

那有人說,寫 redo log 不就是磁盤 IO 嗎?而寫數據到磁盤也是磁盤 IO,既然都是磁盤 IO,那幹嘛不把直接把數據寫到磁盤呢?還費這事!

此言差矣。

寫 redo log 跟寫數據有一個很大的差異,那就是 redo log 是順序 IO,而寫數據涉及到隨機 IO,寫數據需要尋址,找到對應的位置,然後更新/添加/刪除,而寫 redo log 則是在一個固定的位置循環寫入,是順序 IO,所以速度要高於寫數據。

如前文所說,redo log 涉及到兩個東西:redo log buffer 和 redo log file,這兩個東西我們分別來介紹。

4.1 redo log buffer

先來說 redo log buffer。

我們說數據的變化先寫入 redo log 中,並不是上來就寫磁盤,也是先寫到內存中,即 redo log buffer,在時機成熟時,再寫入磁盤,也就是 redo log file。

我們先來看看 redo log buffer 有多大:

16777216 ÷ 1024 ÷ 1024 = 16MB

可以看到,這個 redo log buffer 大小剛好是 16MB,如果你覺得這個值有點小,也可以自行修改其大小。

數據的變更都會首先記錄在這塊內存中。小夥伴們知道,MySQL 的增刪改,如果我們沒有顯式的開啓事務,MySQL 內部也是有一個事務存在的,當內部這個事務 commit 的時候,redo log buffer 會持久化到磁盤中。

具體來說,有如下幾個持久化時機:

  1. innodb_flush_log_at_trx_commit

通過 innodb_flush_log_at_trx_commit 參數來控制持久化時機,該參數默認值爲 1,如下圖:

當然開發者可根據自己的實際需求修改該參數。該參數有三種取值,含義分別如下:

  • 0:每秒一次,將 redo log buffer 中的數據刷新到磁盤中。
  • 1:每次 commit 時,將 redo log buffer 中的數據刷新到磁盤中,即只要 commit 成功,磁盤上就有對應的 redo log 日誌,這是最安全的情況,也是推薦使用的參數
  • 2:每次 commit 時,將 redo log buffer 中的數據刷新到操作系統緩存中,操作系統緩存中的數據每秒刷新一次,會持久化到磁盤中。

這是第一種 redo log buffer 持久化的時機。

  1. 當 redo log buffer 的使用量達到 innodb_log_buffer_size 的一半時,將其寫入磁盤成爲 redo log file。
  2. MySQL 關閉時,將 redo log buffer 寫入磁盤成爲 redo log file。

那如果 redo log buffer 中的數據還沒有磁盤,MySQL 就掛了該怎麼辦?沒寫入磁盤,說明你還沒 commit,既然沒 commit,那就數據修改操作都還沒有完成,那隻能丟了就丟了,如果已經 commit 了,那麼數據就會持久化到 redo log file 中,此時即使 MySQL 掛了,將來 MySQL 重啓恢復了,數據也是可以被恢復的。具體的恢復邏輯,就涉及到兩階段提交了,這個松哥在後面的文章中再和大家詳細介紹。

4.2 redo log 落盤

還有一個需要大家注意的問題就是 redo log 落盤,落盤的數據從哪裏來?是從 redo log 日誌中來還是從 buffer pool 中來?

在前面的文章中我們說過:binlog 是一種邏輯日誌,他裏邊所記錄的是一條 SQL 語句的原始邏輯,例如給某一個字段 +1,這區別於 redo log 的物理日誌,物理日誌記錄的是在某個數據頁上做了什麼修改。

由於 redo log 並沒有記錄數據頁的完整數據,所以正常的落盤其實用不到 redo log,數據落盤的時機到了時,直接拿着將髒頁(buffer pool)持久化到磁盤中即可。

好啦,今天就和大家分享這麼多,redo log 還有一些內容,我們在後面的文章中再繼續聊~

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章