MySQL的redo log和bin log 真香
大家好,我是連邊,這是我的第22篇原創(chuàng)文章。
今天這篇文章給大家?guī)鞰ySQL中重要的兩個日志 - redo log、binlog,從理論概念出發(fā),結合圖解分析,看完這篇文章之后,你能對redo log、binlog有深入的理解。
文章導讀

淺談MySQL分層架構
在講具體的日志之前,先稍微鋪墊下MySQL分層的架構,讓大家知道redo log、binlog是由MySQL的哪一層產(chǎn)生的。

MySQL整體分為3層:客戶端層,Server層和存儲引擎層。我們的binlog日志,由Server層生成,redo log是InnoDB特有的日志,由InnoDB引擎生成。
重做日志(redo log)
什么是redo log
InnoDB為了能夠支持事務一系列操作,而事務有4種特性:原子性、一致性、隔離性、持久性,在事務操作中,要么全部執(zhí)行,要么全部不執(zhí)行,這就是事務的目的。而我們的redo log用來保證事務的持久性,即我們常說的ACID中的D。我們只需要知道它是通過一套什么樣的機制,來保證持久性,就能掌握好redo log。
這里的說的持久性,是說最后落盤到redo log文件(即常見的ib_logfile文件),因為最后我們異常情況的恢復,都是根據(jù)文件來做恢復的。
那么我們的MySQL InnoDB是通過一套什么樣的機制來確保速度與可靠性的呢?
WAL
在計算機體系中,CPU處理速度和硬盤的速度,是不在同一個數(shù)量級上的,為了讓它們速度匹配,從而催生了我們的內(nèi)存模塊,但是內(nèi)存有一個特點,就是掉電之后,數(shù)據(jù)就會丟失,不是持久的,我們需要持久化的數(shù)據(jù),最后都需要存儲到硬盤上。
InnoDB引擎設計者也利用了類似的設計思想,先寫內(nèi)存,再寫硬盤,這樣就不會因為redo log寫硬盤IO而導致數(shù)據(jù)庫性能問題。在InnoDB中,這種技術有一個專業(yè)名稱,叫做Write-Ahead Log(預先日志持久化)

redo log寫入策略
上邊是保證了處理的速度,但是怎么樣保證寫入到硬盤的可靠性呢?
InnoDB引擎的設計者也設計了一種寫入的策略,首先有一個后臺線程,每隔1秒,就會把redo log buffer中的日志,調用write寫到文件系統(tǒng)的page cache,然后調用fsync持久化到磁盤(即redo log文件 ib_logfile0 ib_logfile1)。
為了控制 redo log寫入策略,InnoDB提供了innodb_flush_log_at_trx_commit配置參數(shù),它有三種取值:
設置為 0 的時候,表示每次事務提交時都只是把 redo log 留在 redo log buffer 中 ; 設置為 1 的時候,表示每次事務提交時都將 redo log 直接持久化到磁盤; 設置為 2 的時候,表示每次事務提交時都只是把 redo log 寫到 page cache。
如果不是對性能要求高的,一般把該參數(shù)設置為 1
redo log的擦除
通過上邊的設計,速度和可靠性的問題都解決了,但是我們仔細想想,還會有什么問題?
隨著文件的增加,落盤的速度會越來越慢,直到有一天 ...
聰明的設計者這樣子想著,如果我一直處理小文件,最大不能超過某個大小,不就行了?
也確實是這樣子處理的,但是這里就涉及到一個刪除日志文件的算法,即我們的redo log擦除。
redo log 的大小是固定的,比如可以配置一組4個文件,每個文件大小是8M,那么這個redo log總共就可以記錄32M的操作,這個參數(shù)可以通過innodb_log_file_size設置。
下圖是具體的擦除算法,ib_logfile 從頭開始寫,寫到末尾就又回到開頭循環(huán)寫。

write pos是當前記錄的位置,一邊寫一邊后移,寫到第3號文件末尾后就回到0號文件開頭。checkpoint是當前要擦除的位置,也是往后移動并且循環(huán)的,擦除記錄前要把記錄更新到數(shù)據(jù)文件,write pos與check point之間為剩余可用寫入的空間。
何時會擦除redo log并更新到數(shù)據(jù)文件中
系統(tǒng)空閑時 Redo log文件沒有空閑空間時,即write pos追上check point的時候; MySQL Server正常關閉時
crash-safe
有了以上這一些機制保障,我們可以相信redo log是可靠的,只要持久化到redo log文件中了,InnoDB 就可以保證即使數(shù)據(jù)庫發(fā)生異常重啟,之前提交的記錄都不會丟失,而我們把這個能力稱為 crash-safe。
歸檔日志(binlog)
在寫這篇文章的時候,糾結到底先寫redo log還是binlog,最后還是秉承先苦后甜的原則,把redo log寫在前面了。如果redo log的部分看懂了,binlog掌握是輕松的,跟著我的思路,我們繼續(xù)binlog~
前邊講過,redo log是InnoDB引擎特有的日志,是引擎層面的日志,而在我們的數(shù)據(jù)庫的Server層面,也有自己的日志,稱為binlog(歸檔日志)。
binlog是邏輯日志,怎么樣來理解這個邏輯日志呢?
我們通過查看一段binlog來理解。
理解邏輯日志
這里一大段的操作,都是為了查看binlog文件里邊存儲的是什么內(nèi)容,熟悉的讀者可以直接略過。
執(zhí)行命令,寫入新binlog文件,不讓之前的邏輯影響。
執(zhí)行一次flush logs命令行,就會在data目錄下新增一個mysql-bin.00000x文件
## 登陸MySQL命令行
mysql -uroot -p
## 刷新binlog
flush logs;
## 確認刷新binlog成功
show master status;
## 查詢binlog日志位置
show variables like'log_bin%';

測試數(shù)據(jù)
## 創(chuàng)建表
CREATE TABLE `User` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) CHARACTER SET gb2312 COLLATE gb2312_chinese_ci NOT NULL,
`age` int(11) UNSIGNED NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
## 新增
INSERT `User` VALUES("1", "張三", 18);
INSERT `User` VALUES("2", "李四", 20);
## 修改
DELETE FROM `User` WHERE id = 1;

翻譯binlog二進制文件
sudo /usr/local/mysql/bin/mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000006 > mysqlbin.sql

這是翻譯出來的sql文件,是因為我在mysqlbinlog -v參數(shù)加工而成的。
由此可知,邏輯日志里邊就是記錄著sql語句,通過sql語句記錄著邏輯的變化,比如insert, update等動作,但不是記錄具體數(shù)據(jù),那個由物理日志完成。
與redo log的區(qū)別
redo log是innoDB引擎特有的;binlog是MySQL的Server層實現(xiàn)的,所有引擎都能使用; redo log是循環(huán)寫的,空間固定會用完;binlog是追加寫入的?!白芳訉憽笔侵竍inlog文件寫到一定大小后會切換到下一個,并不會覆蓋以前的日志。
binlog寫入策略
通過與redo log的區(qū)別,我們知道,binlog是追加寫入的,所以與redo log寫入相比,沒有擦除的概念。那么,還有一些什么樣的其他的區(qū)別呢?
binlog的寫入邏輯比較簡單:事務執(zhí)行過程中,先把日志寫到binlog cahce,事務提交的時候,再把binlog cache寫到binlog文件中(落盤)。

從上圖可以看到,每個線程都有自己的binlog cache,但是共用同一份binlog文件。
圖中的write,指的就是把日志寫入到數(shù)據(jù)庫系統(tǒng)的page cache,并沒有把數(shù)據(jù)持久化到磁盤,所有速度很快;
圖中的fsync,才是將數(shù)據(jù)持久化到磁盤的操作。
write 和 fsync 的時機,是由參數(shù) sync_binlog 控制的:
sync_binlog=0 的時候,表示每次提交事務都只 write,不 fsync;
sync_binlog=1 的時候,表示每次提交事務都會執(zhí)行 fsync;
sync_binlog=N(N>1) 的時候,表示每次提交事務都 write,但累積 N 個事務后才 fsync。
因此,在出現(xiàn) IO 瓶頸的場景里,將 sync_binlog 設置成一個比較大的值,可以提升性能。在實際的業(yè)務場景中,考慮到丟失日志量的可控性,一般不建議將這個參數(shù)設成 0,比較常見的是將其設置為 100~1000 中的某個數(shù)值。
但是,將 sync_binlog 設置為 N,對應的風險是:如果主機發(fā)生異常重啟,會丟失最近 N 個事務的 binlog 日志。
引用《極客時間MySQL45講》
淺談兩階段提交
這里講的兩階段提交,就是純粹的指redo log和binlog日志的兩階段提交。
而兩階段提交的目的就是讓redo log和binlog兩個日志邏輯上一致。
如果redo log持久化并進行了提交,而binlog未持久化數(shù)據(jù)庫就crash了,則從庫從binlog拉取數(shù)據(jù)會少于主庫,造成不一致。因此需要內(nèi)部事務來保證兩種日志的一致性。
兩階段提交步驟

將語句執(zhí)行 記錄redo log,并將記錄狀態(tài)設置為prepare 通知Server,已經(jīng)修改好了,可以提交事務了 將更新的內(nèi)容寫入binlog commit,提交事務 將redo log里這個事務相關的記錄狀態(tài)設置為commited
prepare: redolog寫入log buffer,并fsync持久化到磁盤,在redolog事務中記錄2PC的XID,在redolog事務打上prepare標識
commit: binlog寫入log buffer,并fsync持久化到磁盤,在binlog事務中記錄2PC的XID,同時在redolog事務打上commit標識 其中,prepare和commit階段所提到的“事務”,都是指內(nèi)部XA事務,即2PC
恢復步驟
redolog中的事務如果經(jīng)歷了二階段提交中的prepare階段,則會打上prepare標識,如果經(jīng)歷commit階段,則會打上commit標識(此時redolog和binlog均已落盤)。
按順序掃描redolog,如果redolog中的事務既有prepare標識,又有commit標識,就直接提交(復制redolog disk中的數(shù)據(jù)頁到磁盤數(shù)據(jù)頁) 如果redolog事務只有prepare標識,沒有commit標識,則說明當前事務在commit階段crash了,binlog中當前事務是否完整未可知,此時拿著redolog中當前事務的XID(redolog和binlog中事務落盤的標識),去查看binlog中是否存在此XID 如果binlog中有當前事務的XID,則提交事務(復制redolog disk中的數(shù)據(jù)頁到磁盤數(shù)據(jù)頁) 如果binlog中沒有當前事務的XID,則回滾事務(使用undolog來刪除redolog中的對應事務)
可以將mysql redolog和binlog二階段提交和廣義上的二階段提交進行對比,廣義上的二階段提交,若某個參與者超時未收到協(xié)調者的ack通知,則會進行回滾,回滾邏輯需要開發(fā)者在各個參與者中進行記錄。mysql二階段提交是通過xid進行恢復。
