MySQL日志系統
MySQL有兩個重要的日志系統,分別是 redo log (重做日志) 和 bin log (歸檔日志) 。
這兩種日志有以下三點不同。
- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。
- redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”;binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
- redo log 是循環寫的,空間固定會用完;binlog 是可以追加寫的。“追加寫” 是指 binlog 文件寫到一定大小后會切換到下一個,并不會覆蓋以前的日志。
1. redo log
為了理解這個日志,可以舉一個酒店掌柜的例子。說是有一個酒店掌柜,他有一個粉板專門記錄客人賒賬的情況,如果賒賬的人不多,那么他可以把顧客名和賬目寫在板上。但如果賒賬的人多了,粉板總會有記不下的時候,這個時候掌柜一定還有一個專門記錄賒賬的賬本。
如果有人要賒賬或者還賬的話,掌柜一般有兩種做法:
- 一種做法是直接把賬本翻出來,把這次賒的賬加上去或者扣除掉;
- 另一種做法是先在粉板上記下這次的賬,等打烊以后再把賬本翻出來核算。
在生意紅火柜臺很忙時,掌柜一定會選擇后者,因為前者操作實在是太麻煩了。
同樣,在 MySQL 里也有這個問題,如果每一次的更新操作都需要寫進磁盤,然后磁盤也要找到對應的那條記錄,然后再更新,整個過程 IO 成本、查找成本都很高。為了解決這個問題,MySQL 的設計者就用了類似酒店掌柜粉板的思路來提升更新效率。redo log 就相當于是粉板,而 bin log 相當于是賬本。
而粉板和賬本配合的整個過程,其實就是 MySQL 里經常說到的 WAL 技術,WAL 的全稱是 Write-Ahead Logging,它的關鍵點就是先寫日志,再寫磁盤,也就是先寫粉板,等不忙的時候再寫賬本。
InnoDB 的 redo log 是固定大小的,比如可以配置為一組 4 個文件,每個文件的大小是 1GB,那么這塊“粉板”總共就可以記錄 4GB 的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,如下面這個圖所示。

write pos 是當前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭。checkpoint 是當前要擦除的位置,也是往后推移并且循環的,擦除記錄前要把記錄更新到數據文件。
write pos 和 checkpoint 之間的是“粉板”上還空著的部分,可以用來記錄新的操作。如果 write pos 追上 checkpoint,表示“粉板”滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進一下。
有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為 crash-safe。要理解 crash-safe 這個概念,可以想想我們前面賒賬記錄的例子。只要賒賬記錄記在了粉板上或寫在了賬本上,之后即使掌柜忘記了,比如突然停業幾天,恢復生意后依然可以通過賬本和粉板上的數據明確賒賬賬目。
2. bin log
假設我們要執行一下一條簡單的語句,我們來看執行器和 InnoDB 引擎在執行這個簡單的 update 語句時的內部流程。
update
- 執行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則,需要先從磁盤讀入內存,然后再返回。
- 執行器拿到引擎給的行數據,把這個值加上 1,比如原來是 N,現在就是 N+1,得到新的一行數據,再調用引擎接口寫入這行新數據。
- 引擎將這行新數據更新到內存中,同時將這個更新操作記錄到 redo log 里面,此時 redo log 處于 prepare 狀態。然后告知執行器執行完成了,隨時可以提交事務。
- 執行器生成這個操作的 bin log,并把 bin log 寫入磁盤。
- 執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成。
這里需要關注的點是最后三步將寫 redo log 和寫 bin log 打包成一個事務,要保證redo log 和 bin log 的日志是一樣的。