為什么 MySQL 需要 binlog、undo log、redo log 三種日志?
工作或者面試中,經(jīng)常會(huì)遇到 MySQL 數(shù)據(jù)庫(kù) binlog、undo log、redo log 相關(guān)的知識(shí)點(diǎn),今天我們就來(lái)一起深入分析這三種 log。
申明:本文基于 MySQL 8.0.30,默認(rèn)為 InnoDB 引擎;InnoDB 由 Innobase Oy公司所開(kāi)發(fā),2006年五月時(shí)由甲骨文公司并購(gòu)。
前言
在正式進(jìn)入主題之前,我們先看一張 MySQL的架構(gòu)示意圖:
上述示意圖中的紅色字體:binlog、undo log、redo log 就是我們今天的主角。binlog是 server層生成的日記,而 undo log、redo log 是Innodb 存儲(chǔ)引擎層生成的日志
為了對(duì)這三種日志有更好的體感,我們?cè)诒镜匕惭b了 MySQL,然后看下 log日志在磁盤(pán)目錄上的具體位置(此處是Mac os安裝 MySQL):
binlog
binlog,是 binary log的英文縮寫(xiě),翻譯為二進(jìn)制日志或者歸檔日志(帶有業(yè)務(wù)含義),它是從 MySQL 3.23.14版本引入的。binlog是在 MySQL Server層實(shí)現(xiàn),因此所有數(shù)據(jù)庫(kù)引擎都可以使用它。
1.包含的信息
binlog主要包含兩種信息:
- MySQL數(shù)據(jù)庫(kù)所有的表結(jié)構(gòu)變更以及表數(shù)據(jù)修改的二進(jìn)制日志(像 select,show這種查詢(xún)類(lèi)的操作,不會(huì)記錄);
- 每條語(yǔ)句使用更新數(shù)據(jù)多長(zhǎng)時(shí)間的信息;
2.三個(gè)用途
binlog的用途有 3個(gè):
- 歸檔日志
- 主從復(fù)制
- 數(shù)據(jù)恢復(fù)
3.三種類(lèi)型
binlog有 3種類(lèi)型:
- 語(yǔ)句模式(Statement-based logging): 包含產(chǎn)生數(shù)據(jù)更改(插入、更新、刪除)的 SQL語(yǔ)句;
- 行模式(Row-based logging): 用于記錄單個(gè)行的更改,從 MySQL 5.1版本引入;
- 混合模式(Mixed logging): 默認(rèn)使用語(yǔ)句模式,可以按需自動(dòng)切換到行模式,從 MySQL 5.1版本引入;
接下來(lái),我們通過(guò) MySQL的指令來(lái)查看下 binlog文件的信息格式。
首先,生成 binlog,這里以創(chuàng)建一張user表,然后對(duì) user表進(jìn)行增刪改查操作為例,具體 sql執(zhí)行如下圖:
接著,對(duì)上面生成的 binlog進(jìn)行查看,指令和結(jié)果截圖如下:
查看 binlog是否開(kāi)啟,8.0.30 默認(rèn)是開(kāi)啟的
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
1 row in set (0.02 sec)
# 查看所有的binlog日志文件
mysql> show binary logs;
# 查看某個(gè) binlog的具體信息,通過(guò)指令也可以看出binlog是以 event的方式存儲(chǔ)
mysql> show binlog events in 'binlog.000001'
從上面的 binlog日志我們可以看出:在 binlog文件中,并沒(méi)有把我們執(zhí)行的 SQL語(yǔ)句直接存儲(chǔ),而是轉(zhuǎn)換成了內(nèi)部的一些邏輯指令,所以, binlog它是一種邏輯日志。
undo log
undo log, 中文翻譯為撤銷(xiāo)日志或回滾日志,用于事務(wù)回滾,保證了事務(wù) ACID 特性中的原子性(Atomicity),同時(shí)還可以配合 ReadView 實(shí)現(xiàn)多版本控制(MVCC)。
1.相關(guān)參數(shù)
可以通過(guò) 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:一個(gè) undo log文件對(duì)應(yīng)的最大值,默認(rèn) 1G;
- innodb_undo_directory:undo log文件存放的目錄;
- innodb_undo_log_encrypt:是否對(duì) undo log文件開(kāi)啟空間壓縮,默認(rèn)是關(guān)閉;
- innodb_undo_log_truncate:?jiǎn)蝹€(gè)文件超過(guò)最大值時(shí),是否對(duì) undo log文件進(jìn)行切分,默認(rèn)為打開(kāi)狀態(tài);
- innodb_undo_tablespaces:?jiǎn)蝹€(gè)文件超過(guò)最大值時(shí),undo log文件會(huì)被切分為幾份,默認(rèn)是 2;
2.事務(wù)回滾
在事務(wù)提交之前,MySQL 會(huì)將更新前的數(shù)據(jù)記錄到 undo log 日志文件里,當(dāng)事務(wù)回滾時(shí),可以利用 undo log 來(lái)進(jìn)行回滾。如下圖:
每當(dāng) InnoDB 引擎執(zhí)行一條更新操作(修改、刪除、新增)時(shí),就會(huì)生成對(duì)應(yīng)的一條回滾指令記錄在 undo log 里,比如:
- InnoDB 引擎執(zhí)行 insert 操作,則會(huì)在 undo log 日志里面保存一條相反的 delete 語(yǔ)句;比如:insert into t(id, name) values(1,’zhangsan’); 則 undo log 對(duì)應(yīng)的回滾日志為 delete from t where id = 1;
- InnoDB 引擎執(zhí)行 delete 操作,則會(huì)在 undo log 日志里面保存一條相反的 insert 語(yǔ)句;比如:delete from t where id = 1; 則 undo log 對(duì)應(yīng)的回滾日志為 insert into t(id, name) values(1,’zhangsan’);
- InnoDB 引擎執(zhí)行 update 操作,則會(huì)在 undo log 日志里面保存一條相反的 update 語(yǔ)句;比如:update t set name = ‘lisi’ where id = 1; 則 undo log 對(duì)應(yīng)的回滾日志為 update t set name = ‘zhangsan’ where id = 1;
3.undo log 和 ReadView 實(shí)現(xiàn)多版本控制(MVCC)
在 InnoDB引擎中,可以多個(gè)事務(wù)對(duì)同一條數(shù)據(jù)記錄進(jìn)行更新操作,當(dāng)出現(xiàn)異常時(shí),能及時(shí)進(jìn)行數(shù)據(jù)回滾,那么 InnoDB是如何能精確地把數(shù)據(jù)回滾到具體的哪一個(gè)版本呢?這就是 InnoDB的多版本控制機(jī)制。
如下圖:有 3個(gè)事務(wù)分別對(duì)表中id = 1行記錄進(jìn)行更新操作,因此,在undo log文件中就會(huì)產(chǎn)生3條邏輯回滾日志
Redo log
1.redo log
redo log,翻譯成重做日志,用于crash-safe,即當(dāng)數(shù)據(jù)庫(kù)發(fā)生異常重啟,可以保證之前提交的記錄不會(huì)丟失,它是 InnoDB引擎獨(dú)有的日志。
redo log 是物理日志,記錄了某個(gè)數(shù)據(jù)頁(yè)做了什么修改,比如對(duì)某表空間中的 某數(shù)據(jù)頁(yè)某偏移量的地方做了某更新,每當(dāng)執(zhí)行一個(gè)事務(wù)就會(huì)產(chǎn)生這樣的一條或者多條物理日志。
在事務(wù)提交時(shí),只要先將 redo log 持久化到磁盤(pán)即可,可以不需要等到將緩存在 Buffer Pool 里的臟頁(yè)數(shù)據(jù)持久化到磁盤(pán)。
當(dāng)系統(tǒng)崩潰時(shí),雖然臟頁(yè)數(shù)據(jù)沒(méi)有持久化,但是 redo log 已經(jīng)持久化,接著 MySQL 重啟后,可以根據(jù) redo log 的內(nèi)容,將所有數(shù)據(jù)恢復(fù)到最新的狀態(tài)。
2.為什么需要 redo log?
為了防止斷電導(dǎo)致數(shù)據(jù)丟失的問(wèn)題,當(dāng)有一條記錄需要更新的時(shí)候,InnoDB 引擎就會(huì)先更新內(nèi)存(同時(shí)標(biāo)記為臟頁(yè)),然后將本次對(duì)這個(gè)頁(yè)的修改以 redo log 的形式記錄下來(lái),這個(gè)時(shí)候更新就算完成了。
后續(xù),InnoDB 引擎會(huì)在適當(dāng)?shù)臅r(shí)候,由后臺(tái)線(xiàn)程將緩存在 Buffer Pool 的臟頁(yè)刷新到磁盤(pán)里,這就是 WAL (Write-Ahead Logging)技術(shù)。
WAL 技術(shù)指的是, MySQL 的寫(xiě)操作并不是立刻寫(xiě)到磁盤(pán)上,而是先寫(xiě)日志,然后在合適的時(shí)間再寫(xiě)到磁盤(pán)上。
整個(gè)過(guò)程如下圖:
常見(jiàn)問(wèn)題
1.MySQL 如何辨別 binlog 的完整性?
- statement 格式的 binlog,文件末尾有 COMMIT;
- row 格式的 binlog,文件末尾有一個(gè) XID event。
2.redo log 和 binlog 是怎么關(guān)聯(lián)起來(lái)的?
它們有一個(gè)共同的數(shù)據(jù)字段,叫 XID。崩潰恢復(fù)的時(shí)候,會(huì)按順序掃描 redo log:如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;如果碰到只有 parepare、而沒(méi)有 commit 的 redo log,就拿著 XID 去 binlog 找對(duì)應(yīng)的事務(wù)。
3.處于 prepare 階段的 redo log 加上完整 binlog,重啟就能恢復(fù),MySQL 為什么要這么設(shè)計(jì)?
其實(shí),這個(gè)問(wèn)題還是跟我們?cè)诜醋C法中說(shuō)到的數(shù)據(jù)與備份的一致性有關(guān)。在時(shí)刻 B,也就是 binlog 寫(xiě)完以后 MySQL 發(fā)生崩潰,這時(shí)候 binlog 已經(jīng)寫(xiě)入了,之后就會(huì)被從庫(kù)(或者用這個(gè) binlog 恢復(fù)出來(lái)的庫(kù))使用。所以,在主庫(kù)上也要提交這個(gè)事務(wù)。采用這個(gè)策略,主庫(kù)和備庫(kù)的數(shù)據(jù)就保證了一致性。
4.為什么需要兩階段提交呢?
兩階段提交是經(jīng)典的分布式系統(tǒng)問(wèn)題,并不是 MySQL 獨(dú)有的。如果必須要舉一個(gè)場(chǎng)景,來(lái)說(shuō)明這么做的必要性的話(huà),那就是事務(wù)的持久性問(wèn)題。對(duì)于 InnoDB 引擎來(lái)說(shuō),如果 redo log 提交完成了,事務(wù)就不能回滾(如果這還允許回滾,就可能覆蓋掉別的事務(wù)的更新)。而如果 redo log 直接提交,然后 binlog 寫(xiě)入的時(shí)候失敗,InnoDB 又回滾不了,數(shù)據(jù)和 binlog 日志又不一致了。兩階段提交就是為了給所有人一個(gè)機(jī)會(huì),當(dāng)每個(gè)人都說(shuō)“我 ok”的時(shí)候,再一起提交。
總結(jié)
- undo log(回滾日志):是 Innodb 存儲(chǔ)引擎層的邏輯日志,實(shí)現(xiàn)了事務(wù)中的原子性,主要用于事務(wù)回滾和 MVCC。
- redo log(重做日志):是 Innodb 存儲(chǔ)引擎層的物理日志,是循環(huán)寫(xiě),實(shí)現(xiàn)了事務(wù)中的持久性,主要用于掉電等故障恢復(fù);
- binlog (歸檔日志):是 Server 層生成的日志,所有引擎都可使用,主要用于數(shù)據(jù)備份、數(shù)據(jù)恢復(fù)和主從復(fù)制;