今天想和大家聊一聊 MySQL 中的 redo log,其實最早我是想聊兩階段提交的,后來想想可能有小伙伴還不了解 binlog,所以就先整了一篇 binlog:
- 手把手教你玩 MySQL 刪庫不跑路,直接把 MySQL 的 binlog 玩溜!
- MySQL刪庫不跑路(視頻版)
binlog 大家懂了之后,接下來還差個 redo log,redo log 大家也懂了,那么再講兩階段提交相信小伙伴們就很容易懂了,咱們一步一步來。
1. 誰的 redo log
學(xué)習(xí) redo log,我覺得首先要搞明白一個問題,就是是誰的 redo log?
我們知道,MySQL 架構(gòu)整體上分為兩層:Server 層和存儲引擎層,如下圖:
前面松哥文章+視頻跟大家聊的 binlog,是 MySQL 自己提供的 binlog,而 redo log 則不是 MySQL 提供的,而是存儲引擎 InnoDB 自己提供的。所以在 MySQL 中就存在兩類日志 binlog 和 redo log,存在兩類日志既有歷史原因(InnoDB 最早不是 MySQL 官方存儲引擎)也有技術(shù)原因,這個咱們以后再細聊。
先把這個問題搞清楚,后面很多地方就容易懂了。
2. buffer pool
在正式介紹 redo log 之前,還有一個 buffer pool 需要大家了解。
小伙伴們知道,InnoDB 引擎存儲數(shù)據(jù)的時候,是以頁為單位的,每個數(shù)據(jù)頁的大小默認是 16KB,我們可以通過如下命令來查看頁的大小:
16384/1024=16
剛好是 16KB。
計算機在存儲數(shù)據(jù)的時候,最小存儲單元是扇區(qū),一個扇區(qū)的大小是 512 字節(jié),而文件系統(tǒng)(例如 XFS/EXT4)最小單元是塊,一個塊的大小是 4KB,也就是四個塊組成一個 InnoDB 中的頁。我們在 MySQL 中針對數(shù)據(jù)庫的增刪改查操作,都是操作數(shù)據(jù)頁,說白了,就是操作磁盤。
但是大家想想,如果每一次操作都操作磁盤,那么就會產(chǎn)生海量的磁盤 IO 操作,如果是傳統(tǒng)的機械硬盤,還會涉及到很多隨機 IO 操作,效率低的令人發(fā)指。這嚴重影響了 MySQL 的性能。
為了解決這一問題,MySQL 引入了 buffer pool,也就是我們常說的緩沖池。
buffer pool 的主要作用就是緩存索引和表數(shù)據(jù),以避免每一次操作都要進行磁盤 IO,通過 buffer pool 可以提高數(shù)據(jù)的訪問速度。
通過如下命令可以查看 buffer pool 的默認大小:
134217728/1024/1024=128
默認大小是 128MB,因為松哥這里的 MySQL 是安裝在 Docker 中,所以這個分配的小一些。一般來說,如果一個服務(wù)器只是運行了一個 MySQL 服務(wù),我們可以設(shè)置 buffer pool 的大小為服務(wù)器內(nèi)存大小的 75%~80%。
3. change buffer
在正式介紹 redo log 之前,還有一個 change buffer 需要大家了解。
前面我們說的 buffer pool 雖然提高了訪問速度,但是增刪改的效率并沒有因此提升,當涉及到增刪改的時候,還是需要磁盤 IO,那么效率一樣低的令人發(fā)指。
為了解決這個問題,MySQL 中引入了 change buffer。change buffer 以前并不叫這個名字,以前叫 insert buffer,即只針對 insert 操作有效,現(xiàn)在改名叫 change buffer 了,不僅僅針對 insert 有效,對 delete 和 update 操作也是有效的,change buffer 主要是對非唯一的索引有效,如果字段是唯一性索引,那么更新的時候要去檢查唯一性,依然無法避免磁盤 IO。
change buffer 就是說,當我們需要更改數(shù)據(jù)庫中的數(shù)據(jù)的時候,我們把更改記錄到內(nèi)存中,等到將來數(shù)據(jù)被讀取的時候,再將內(nèi)存中的數(shù)據(jù) merge 到 buffer pool 然后返回,此時 buffer pool 中的數(shù)據(jù)和磁盤中的數(shù)據(jù)就會有差異,有差異的數(shù)據(jù)我們稱之為臟頁,在滿足條件的時候(redo log 寫滿了、內(nèi)存寫滿了、其他空閑時候),InnoDB 會把臟頁刷新回磁盤。這種方式可以有效降低寫操作的磁盤 IO,提升數(shù)據(jù)庫的性能。
通過如下命令我們可以查看 change buffer 的大小以及哪些操作會涉及到 change buffer:
- innodb_change_buffer_max_size:這個配置表示 change buffer 的大小占整個緩沖池的比例,默認值是 25%,最大值是 50%。
- innodb_change_buffering:這個操作表示哪些寫操作會用到 change buffer,默認的 all 表示所有寫操作,我們也可以自己設(shè)置為 none/inserts/deletes/changes/purges 等。
不過 change buffer 和 buffer pool 都涉及到內(nèi)存操作,數(shù)據(jù)不能持久化,那么,當存在臟頁的時候,MySQL 如果突然掛了,就有可能造成數(shù)據(jù)丟失(因為內(nèi)存中的數(shù)據(jù)還沒寫到磁盤上),但是我們在實際使用 MySQL 的時候,其實并不會有這個問題,那么問題是怎么解決的?那就得靠 redo log 了。
4. redo log 的誕生
在正式介紹 redo log 之前,還需要給大家普及一個概念:WAL。
WAL 全稱是 Write-Ahead Logging 中文譯作預(yù)寫日志。啥意思呢?就是說 MySQL 的寫操作并不是立刻更新到磁盤上,而是先記錄在日志上,然后在合適的時間再更新到磁盤上,這樣的好處是錯開高峰期的磁盤 IO,提高 MySQL 的性能。
配合上前面的 buffer pool 和 change buffer,WAL 就是說在操作 buffer pool 和 change buffer 之前,會先把記錄寫到 redo log 日志中,然后再去更新 buffer pool 或者 change buffer,這樣,即使系統(tǒng)突然崩了,將來也可以通過 redo log 恢復(fù)數(shù)據(jù)。當然,redo log 本身又分為:
- 日志緩沖(redo log buffer),該部分日志是易失性的。
- 重做日志(redo log file),這是磁盤上的日志文件,該部分日志是持久的。
那有人說,寫 redo log 不就是磁盤 IO 嗎?而寫數(shù)據(jù)到磁盤也是磁盤 IO,既然都是磁盤 IO,那干嘛不把直接把數(shù)據(jù)寫到磁盤呢?還費這事!
此言差矣。
寫 redo log 跟寫數(shù)據(jù)有一個很大的差異,那就是 redo log 是順序 IO,而寫數(shù)據(jù)涉及到隨機 IO,寫數(shù)據(jù)需要尋址,找到對應(yīng)的位置,然后更新/添加/刪除,而寫 redo log 則是在一個固定的位置循環(huán)寫入,是順序 IO,所以速度要高于寫數(shù)據(jù)。
如前文所說,redo log 涉及到兩個東西:redo log buffer 和 redo log file,這兩個東西我們分別來介紹。
4.1 redo log buffer
先來說 redo log buffer。
我們說數(shù)據(jù)的變化先寫入 redo log 中,并不是上來就寫磁盤,也是先寫到內(nèi)存中,即 redo log buffer,在時機成熟時,再寫入磁盤,也就是 redo log file。
我們先來看看 redo log buffer 有多大:
16777216 ÷ 1024 ÷ 1024 = 16MB
可以看到,這個 redo log buffer 大小剛好是 16MB,如果你覺得這個值有點小,也可以自行修改其大小。
數(shù)據(jù)的變更都會首先記錄在這塊內(nèi)存中。小伙伴們知道,MySQL 的增刪改,如果我們沒有顯式的開啟事務(wù),MySQL 內(nèi)部也是有一個事務(wù)存在的,當內(nèi)部這個事務(wù) commit 的時候,redo log buffer 會持久化到磁盤中。
具體來說,有如下幾個持久化時機:
- innodb_flush_log_at_trx_commit
通過
innodb_flush_log_at_trx_commit 參數(shù)來控制持久化時機,該參數(shù)默認值為 1,如下圖:
當然開發(fā)者可根據(jù)自己的實際需求修改該參數(shù)。該參數(shù)有三種取值,含義分別如下:
- 0:每秒一次,將 redo log buffer 中的數(shù)據(jù)刷新到磁盤中。
- 1:每次 commit 時,將 redo log buffer 中的數(shù)據(jù)刷新到磁盤中,即只要 commit 成功,磁盤上就有對應(yīng)的 redo log 日志,這是最安全的情況,也是推薦使用的參數(shù)。
- 2:每次 commit 時,將 redo log buffer 中的數(shù)據(jù)刷新到操作系統(tǒng)緩存中,操作系統(tǒng)緩存中的數(shù)據(jù)每秒刷新一次,會持久化到磁盤中。
這是第一種 redo log buffer 持久化的時機。
- 當 redo log buffer 的使用量達到 innodb_log_buffer_size 的一半時,將其寫入磁盤成為 redo log file。
- MySQL 關(guān)閉時,將 redo log buffer 寫入磁盤成為 redo log file。
那如果 redo log buffer 中的數(shù)據(jù)還沒有磁盤,MySQL 就掛了該怎么辦?沒寫入磁盤,說明你還沒 commit,既然沒 commit,那就數(shù)據(jù)修改操作都還沒有完成,那只能丟了就丟了,如果已經(jīng) commit 了,那么數(shù)據(jù)就會持久化到 redo log file 中,此時即使 MySQL 掛了,將來 MySQL 重啟恢復(fù)了,數(shù)據(jù)也是可以被恢復(fù)的。具體的恢復(fù)邏輯,就涉及到兩階段提交了,這個松哥在后面的文章中再和大家詳細介紹。
4.2 redo log 落盤
還有一個需要大家注意的問題就是 redo log 落盤,落盤的數(shù)據(jù)從哪里來?是從 redo log 日志中來還是從 buffer pool 中來?
在前面的文章中我們說過:binlog 是一種邏輯日志,他里邊所記錄的是一條 SQL 語句的原始邏輯,例如給某一個字段 +1,這區(qū)別于 redo log 的物理日志,物理日志記錄的是在某個數(shù)據(jù)頁上做了什么修改。
由于 redo log 并沒有記錄數(shù)據(jù)頁的完整數(shù)據(jù),所以正常的落盤其實用不到 redo log,數(shù)據(jù)落盤的時機到了時,直接拿著將臟頁(buffer pool)持久化到磁盤中即可。
好啦,今天就和大家分享這么多,redo log 還有一些內(nèi)容,我們在后面的文章中再繼續(xù)聊~
參考資料:
- https://www.cnblogs.com/ZhuChangwu/p/14096575.html