為什么 MySQL 需要 binlog、undo log、redo log 三種日志?
工作或者面試中,經(jīng)常會遇到 MySQL 數(shù)據(jù)庫 binlog、undo log、redo log 相關(guān)的知識點(diǎn),今天我們就來一起深入分析這三種 log。
申明:本文基于 MySQL 8.0.30,默認(rèn)為 InnoDB 引擎;InnoDB 由 Innobase Oy公司所開發(fā),2006年五月時由甲骨文公司并購。

前言
在正式進(jìn)入主題之前,我們先看一張 MySQL的架構(gòu)示意圖:

上述示意圖中的紅色字體:binlog、undo log、redo log 就是我們今天的主角。binlog是 server層生成的日記,而 undo log、redo log 是Innodb 存儲引擎層生成的日志
為了對這三種日志有更好的體感,我們在本地安裝了 MySQL,然后看下 log日志在磁盤目錄上的具體位置(此處是Mac os安裝 MySQL):

binlog
binlog,是 binary log的英文縮寫,翻譯為二進(jìn)制日志或者歸檔日志(帶有業(yè)務(wù)含義),它是從 MySQL 3.23.14版本引入的。binlog是在 MySQL Server層實(shí)現(xiàn),因此所有數(shù)據(jù)庫引擎都可以使用它。
1.包含的信息
binlog主要包含兩種信息:
- MySQL數(shù)據(jù)庫所有的表結(jié)構(gòu)變更以及表數(shù)據(jù)修改的二進(jìn)制日志(像 select,show這種查詢類的操作,不會記錄);
- 每條語句使用更新數(shù)據(jù)多長時間的信息;
2.三個用途
binlog的用途有 3個:
- 歸檔日志
- 主從復(fù)制
- 數(shù)據(jù)恢復(fù)
3.三種類型
binlog有 3種類型:
- 語句模式(Statement-based logging): 包含產(chǎn)生數(shù)據(jù)更改(插入、更新、刪除)的 SQL語句;
- 行模式(Row-based logging): 用于記錄單個行的更改,從 MySQL 5.1版本引入;
- 混合模式(Mixed logging): 默認(rèn)使用語句模式,可以按需自動切換到行模式,從 MySQL 5.1版本引入;
接下來,我們通過 MySQL的指令來查看下 binlog文件的信息格式。
首先,生成 binlog,這里以創(chuàng)建一張user表,然后對 user表進(jìn)行增刪改查操作為例,具體 sql執(zhí)行如下圖:

接著,對上面生成的 binlog進(jìn)行查看,指令和結(jié)果截圖如下:
查看 binlog是否開啟,8.0.30 默認(rèn)是開啟的
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
1 row in set (0.02 sec)
# 查看所有的binlog日志文件
mysql> show binary logs;
# 查看某個 binlog的具體信息,通過指令也可以看出binlog是以 event的方式存儲
mysql> show binlog events in 'binlog.000001'
從上面的 binlog日志我們可以看出:在 binlog文件中,并沒有把我們執(zhí)行的 SQL語句直接存儲,而是轉(zhuǎn)換成了內(nèi)部的一些邏輯指令,所以, binlog它是一種邏輯日志。
undo log
undo log, 中文翻譯為撤銷日志或回滾日志,用于事務(wù)回滾,保證了事務(wù) ACID 特性中的原子性(Atomicity),同時還可以配合 ReadView 實(shí)現(xiàn)多版本控制(MVCC)。
1.相關(guān)參數(shù)
可以通過 show variables like ‘%undo%’; 指令查看 undo log相關(guān)參數(shù):
mysql> show variables like '%undo%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| innodb_max_undo_log_size | 1073741824 |
| innodb_undo_directory | ./ |
| innodb_undo_log_encrypt | OFF |
| innodb_undo_log_truncate | ON |
| innodb_undo_tablespaces | 2 |
+--------------------------+------------+
5 rows in set (0.01 sec)- innodb_max_undo_log_size:一個 undo log文件對應(yīng)的最大值,默認(rèn) 1G;
- innodb_undo_directory:undo log文件存放的目錄;
- innodb_undo_log_encrypt:是否對 undo log文件開啟空間壓縮,默認(rèn)是關(guān)閉;
- innodb_undo_log_truncate:單個文件超過最大值時,是否對 undo log文件進(jìn)行切分,默認(rèn)為打開狀態(tài);
- innodb_undo_tablespaces:單個文件超過最大值時,undo log文件會被切分為幾份,默認(rèn)是 2;
2.事務(wù)回滾
在事務(wù)提交之前,MySQL 會將更新前的數(shù)據(jù)記錄到 undo log 日志文件里,當(dāng)事務(wù)回滾時,可以利用 undo log 來進(jìn)行回滾。如下圖:

每當(dāng) InnoDB 引擎執(zhí)行一條更新操作(修改、刪除、新增)時,就會生成對應(yīng)的一條回滾指令記錄在 undo log 里,比如:
- InnoDB 引擎執(zhí)行 insert 操作,則會在 undo log 日志里面保存一條相反的 delete 語句;比如:insert into t(id, name) values(1,’zhangsan’); 則 undo log 對應(yīng)的回滾日志為 delete from t where id = 1;
- InnoDB 引擎執(zhí)行 delete 操作,則會在 undo log 日志里面保存一條相反的 insert 語句;比如:delete from t where id = 1; 則 undo log 對應(yīng)的回滾日志為 insert into t(id, name) values(1,’zhangsan’);
- InnoDB 引擎執(zhí)行 update 操作,則會在 undo log 日志里面保存一條相反的 update 語句;比如:update t set name = ‘lisi’ where id = 1; 則 undo log 對應(yīng)的回滾日志為 update t set name = ‘zhangsan’ where id = 1;
3.undo log 和 ReadView 實(shí)現(xiàn)多版本控制(MVCC)
在 InnoDB引擎中,可以多個事務(wù)對同一條數(shù)據(jù)記錄進(jìn)行更新操作,當(dāng)出現(xiàn)異常時,能及時進(jìn)行數(shù)據(jù)回滾,那么 InnoDB是如何能精確地把數(shù)據(jù)回滾到具體的哪一個版本呢?這就是 InnoDB的多版本控制機(jī)制。
如下圖:有 3個事務(wù)分別對表中id = 1行記錄進(jìn)行更新操作,因此,在undo log文件中就會產(chǎn)生3條邏輯回滾日志

Redo log
1.redo log
redo log,翻譯成重做日志,用于crash-safe,即當(dāng)數(shù)據(jù)庫發(fā)生異常重啟,可以保證之前提交的記錄不會丟失,它是 InnoDB引擎獨(dú)有的日志。
redo log 是物理日志,記錄了某個數(shù)據(jù)頁做了什么修改,比如對某表空間中的 某數(shù)據(jù)頁某偏移量的地方做了某更新,每當(dāng)執(zhí)行一個事務(wù)就會產(chǎn)生這樣的一條或者多條物理日志。
在事務(wù)提交時,只要先將 redo log 持久化到磁盤即可,可以不需要等到將緩存在 Buffer Pool 里的臟頁數(shù)據(jù)持久化到磁盤。
當(dāng)系統(tǒng)崩潰時,雖然臟頁數(shù)據(jù)沒有持久化,但是 redo log 已經(jīng)持久化,接著 MySQL 重啟后,可以根據(jù) redo log 的內(nèi)容,將所有數(shù)據(jù)恢復(fù)到最新的狀態(tài)。
2.為什么需要 redo log?
為了防止斷電導(dǎo)致數(shù)據(jù)丟失的問題,當(dāng)有一條記錄需要更新的時候,InnoDB 引擎就會先更新內(nèi)存(同時標(biāo)記為臟頁),然后將本次對這個頁的修改以 redo log 的形式記錄下來,這個時候更新就算完成了。
后續(xù),InnoDB 引擎會在適當(dāng)?shù)臅r候,由后臺線程將緩存在 Buffer Pool 的臟頁刷新到磁盤里,這就是 WAL (Write-Ahead Logging)技術(shù)。
WAL 技術(shù)指的是, MySQL 的寫操作并不是立刻寫到磁盤上,而是先寫日志,然后在合適的時間再寫到磁盤上。
整個過程如下圖:

常見問題
1.MySQL 如何辨別 binlog 的完整性?
- statement 格式的 binlog,文件末尾有 COMMIT;
- row 格式的 binlog,文件末尾有一個 XID event。
2.redo log 和 binlog 是怎么關(guān)聯(lián)起來的?
它們有一個共同的數(shù)據(jù)字段,叫 XID。崩潰恢復(fù)的時候,會按順序掃描 redo log:如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;如果碰到只有 parepare、而沒有 commit 的 redo log,就拿著 XID 去 binlog 找對應(yīng)的事務(wù)。
3.處于 prepare 階段的 redo log 加上完整 binlog,重啟就能恢復(fù),MySQL 為什么要這么設(shè)計(jì)?
其實(shí),這個問題還是跟我們在反證法中說到的數(shù)據(jù)與備份的一致性有關(guān)。在時刻 B,也就是 binlog 寫完以后 MySQL 發(fā)生崩潰,這時候 binlog 已經(jīng)寫入了,之后就會被從庫(或者用這個 binlog 恢復(fù)出來的庫)使用。所以,在主庫上也要提交這個事務(wù)。采用這個策略,主庫和備庫的數(shù)據(jù)就保證了一致性。
4.為什么需要兩階段提交呢?
兩階段提交是經(jīng)典的分布式系統(tǒng)問題,并不是 MySQL 獨(dú)有的。如果必須要舉一個場景,來說明這么做的必要性的話,那就是事務(wù)的持久性問題。對于 InnoDB 引擎來說,如果 redo log 提交完成了,事務(wù)就不能回滾(如果這還允許回滾,就可能覆蓋掉別的事務(wù)的更新)。而如果 redo log 直接提交,然后 binlog 寫入的時候失敗,InnoDB 又回滾不了,數(shù)據(jù)和 binlog 日志又不一致了。兩階段提交就是為了給所有人一個機(jī)會,當(dāng)每個人都說“我 ok”的時候,再一起提交。
總結(jié)
- undo log(回滾日志):是 Innodb 存儲引擎層的邏輯日志,實(shí)現(xiàn)了事務(wù)中的原子性,主要用于事務(wù)回滾和 MVCC。
- redo log(重做日志):是 Innodb 存儲引擎層的物理日志,是循環(huán)寫,實(shí)現(xiàn)了事務(wù)中的持久性,主要用于掉電等故障恢復(fù);
- binlog (歸檔日志):是 Server 層生成的日志,所有引擎都可使用,主要用于數(shù)據(jù)備份、數(shù)據(jù)恢復(fù)和主從復(fù)制;
































