為什么 MySQL 需要 binlog、undo log、redo log 三種日志?
工作或者面試中,經(jīng)常會遇到 MySQL 數(shù)據(jù)庫 binlog、undo log、redo log 相關(guān)的知識點,今天我們就來一起深入分析這三種 log。
申明:本文基于 MySQL 8.0.30,默認為 InnoDB 引擎;InnoDB 由 Innobase Oy公司所開發(fā),2006年五月時由甲骨文公司并購。
前言
在正式進入主題之前,我們先看一張 MySQL的架構(gòu)示意圖:
上述示意圖中的紅色字體:binlog、undo log、redo log 就是我們今天的主角。binlog是 server層生成的日記,而 undo log、redo log 是Innodb 存儲引擎層生成的日志
為了對這三種日志有更好的體感,我們在本地安裝了 MySQL,然后看下 log日志在磁盤目錄上的具體位置(此處是Mac os安裝 MySQL):
binlog
binlog,是 binary log的英文縮寫,翻譯為二進制日志或者歸檔日志(帶有業(yè)務(wù)含義),它是從 MySQL 3.23.14版本引入的。binlog是在 MySQL Server層實現(xiàn),因此所有數(shù)據(jù)庫引擎都可以使用它。
1.包含的信息
binlog主要包含兩種信息:
- MySQL數(shù)據(jù)庫所有的表結(jié)構(gòu)變更以及表數(shù)據(jù)修改的二進制日志(像 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): 默認使用語句模式,可以按需自動切換到行模式,從 MySQL 5.1版本引入;
接下來,我們通過 MySQL的指令來查看下 binlog文件的信息格式。
首先,生成 binlog,這里以創(chuàng)建一張user表,然后對 user表進行增刪改查操作為例,具體 sql執(zhí)行如下圖:
接著,對上面生成的 binlog進行查看,指令和結(jié)果截圖如下:
查看 binlog是否開啟,8.0.30 默認是開啟的
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 實現(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)的最大值,默認 1G;
- innodb_undo_directory:undo log文件存放的目錄;
- innodb_undo_log_encrypt:是否對 undo log文件開啟空間壓縮,默認是關(guān)閉;
- innodb_undo_log_truncate:單個文件超過最大值時,是否對 undo log文件進行切分,默認為打開狀態(tài);
- innodb_undo_tablespaces:單個文件超過最大值時,undo log文件會被切分為幾份,默認是 2;
2.事務(wù)回滾
在事務(wù)提交之前,MySQL 會將更新前的數(shù)據(jù)記錄到 undo log 日志文件里,當事務(wù)回滾時,可以利用 undo log 來進行回滾。如下圖:
每當 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 實現(xiàn)多版本控制(MVCC)
在 InnoDB引擎中,可以多個事務(wù)對同一條數(shù)據(jù)記錄進行更新操作,當出現(xiàn)異常時,能及時進行數(shù)據(jù)回滾,那么 InnoDB是如何能精確地把數(shù)據(jù)回滾到具體的哪一個版本呢?這就是 InnoDB的多版本控制機制。
如下圖:有 3個事務(wù)分別對表中id = 1行記錄進行更新操作,因此,在undo log文件中就會產(chǎn)生3條邏輯回滾日志
Redo log
1.redo log
redo log,翻譯成重做日志,用于crash-safe,即當數(shù)據(jù)庫發(fā)生異常重啟,可以保證之前提交的記錄不會丟失,它是 InnoDB引擎獨有的日志。
redo log 是物理日志,記錄了某個數(shù)據(jù)頁做了什么修改,比如對某表空間中的 某數(shù)據(jù)頁某偏移量的地方做了某更新,每當執(zhí)行一個事務(wù)就會產(chǎn)生這樣的一條或者多條物理日志。
在事務(wù)提交時,只要先將 redo log 持久化到磁盤即可,可以不需要等到將緩存在 Buffer Pool 里的臟頁數(shù)據(jù)持久化到磁盤。
當系統(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ù)丟失的問題,當有一條記錄需要更新的時候,InnoDB 引擎就會先更新內(nèi)存(同時標記為臟頁),然后將本次對這個頁的修改以 redo log 的形式記錄下來,這個時候更新就算完成了。
后續(xù),InnoDB 引擎會在適當?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è)計?
其實,這個問題還是跟我們在反證法中說到的數(shù)據(jù)與備份的一致性有關(guān)。在時刻 B,也就是 binlog 寫完以后 MySQL 發(fā)生崩潰,這時候 binlog 已經(jīng)寫入了,之后就會被從庫(或者用這個 binlog 恢復(fù)出來的庫)使用。所以,在主庫上也要提交這個事務(wù)。采用這個策略,主庫和備庫的數(shù)據(jù)就保證了一致性。
4.為什么需要兩階段提交呢?
兩階段提交是經(jīng)典的分布式系統(tǒng)問題,并不是 MySQL 獨有的。如果必須要舉一個場景,來說明這么做的必要性的話,那就是事務(wù)的持久性問題。對于 InnoDB 引擎來說,如果 redo log 提交完成了,事務(wù)就不能回滾(如果這還允許回滾,就可能覆蓋掉別的事務(wù)的更新)。而如果 redo log 直接提交,然后 binlog 寫入的時候失敗,InnoDB 又回滾不了,數(shù)據(jù)和 binlog 日志又不一致了。兩階段提交就是為了給所有人一個機會,當每個人都說“我 ok”的時候,再一起提交。
總結(jié)
- undo log(回滾日志):是 Innodb 存儲引擎層的邏輯日志,實現(xiàn)了事務(wù)中的原子性,主要用于事務(wù)回滾和 MVCC。
- redo log(重做日志):是 Innodb 存儲引擎層的物理日志,是循環(huán)寫,實現(xiàn)了事務(wù)中的持久性,主要用于掉電等故障恢復(fù);
- binlog (歸檔日志):是 Server 層生成的日志,所有引擎都可使用,主要用于數(shù)據(jù)備份、數(shù)據(jù)恢復(fù)和主從復(fù)制;