面試不用慌!跟著老司機(jī)吃透Redo log 與 Binlog
本文轉(zhuǎn)載自微信公眾號「石杉的架構(gòu)筆記」,作者崔皓。轉(zhuǎn)載本文請聯(lián)系石杉的架構(gòu)筆記公眾號。
MySQL是常用的數(shù)據(jù)庫存儲應(yīng)用,我們利用它存儲信息、查詢信息、處理事務(wù)。特別是為了提高可用性會用到事務(wù)一致性、主從復(fù)制、數(shù)據(jù)恢復(fù)等功能。我們在使用這些功能的時候,是否想過其背后有哪些原理和機(jī)制在支撐?今天我們聚焦redo log和binlog兩個MySQL的日志機(jī)制,以及它們是如何配合提高M(jìn)ySQL存儲可靠性的。今天會學(xué)到以下內(nèi)容:
- Redo log
Redo log 解決了什么問題?
Redo log的執(zhí)行流程
Redo log的寫入方式
Redo log記錄形式
- Binlog
a.Binlog解決了什么問題?
b.Binlog的日志格式
- Redo log 與Binlog的區(qū)別與合作
Redo log
1.Redo log 解決了什么問題?
MySQL應(yīng)用中處理事務(wù)是一個重要的任務(wù),而在事務(wù)處理的四個特性中(ACID),存在一個持久性(Durability),它表示在事務(wù)執(zhí)行過程中,對數(shù)據(jù)的所有改動都必須在事務(wù)成功結(jié)束前保存至某種物理存儲設(shè)備中。
換句話說,只要事務(wù)提交成功,那么對數(shù)據(jù)庫做的修改就被永久保存下來了,不可能因為任何原因再回到原來的狀態(tài)。那么為什么要在MySQL中考慮事務(wù)持久性的問題呢?假設(shè)這么一種場景,當(dāng)數(shù)據(jù)存儲的事務(wù)正在執(zhí)行但是數(shù)據(jù)還沒有保存的時候,數(shù)據(jù)庫宕機(jī)了,那么這些沒來得及存儲到磁盤的數(shù)據(jù)就丟失了,如果此時有一種機(jī)制能夠記錄這個事務(wù)的操作,當(dāng)數(shù)據(jù)庫服務(wù)恢復(fù)的時候,運(yùn)行記錄的操作那么這些沒有來得及存儲的數(shù)據(jù)就能夠正確保存了。
Redo log 就是通過這種手段來實現(xiàn)事務(wù)持久性的。上面的場景中是數(shù)據(jù)庫服務(wù)器宕機(jī),如果發(fā)生其他故障導(dǎo)致尚有臟頁未寫入磁盤的場景,也是可以通過Redo log恢復(fù)的。
1.Redo log的執(zhí)行流程
了解了為什么使用redo log 以后再來看看其執(zhí)行流程,如圖1 所示:
圖1 redo log執(zhí)行流程
該泳道圖由MySQL客戶端、MySQL Server 層和MySQL 存儲引擎層組成,由于redo log是在Innodb存儲引擎中使用的,這里假設(shè)存儲引擎就是Innodb。由于MySQL Server 層主要負(fù)責(zé)SQL語句的分析、優(yōu)化和執(zhí)行工作,而MySQL存儲引擎層主要負(fù)責(zé)存儲工作,redo log 也運(yùn)行在這一層。
跟隨圖中的序號來看看redo log 的運(yùn)行流程。
- 1. 從MySQL客戶端請求語句“update T set a=1 where id=2”,并發(fā)現(xiàn)MySQL Server 層。
- 2. 接收到SQL請求以后MySQL Server 層會對其進(jìn)行分析、優(yōu)化、執(zhí)行等處理工作,將生成的SQL執(zhí)行計劃發(fā)到存儲引擎層執(zhí)行。
- 3. 存儲引擎層將“a修改為1”的這個操作記錄到內(nèi)存中。
- 4. 記錄到內(nèi)存以后會修改redo log 的記錄,會在添加一行記錄,其內(nèi)容是“需要在哪個數(shù)據(jù)頁上做什么修改”。
- 5. 此后,將事務(wù)的狀態(tài)設(shè)置為prepare ,說明已經(jīng)準(zhǔn)備好提交事務(wù)了。
- 6. 等到MySQL Server 層處理完事務(wù)以后,會將事務(wù)的狀態(tài)設(shè)置為commit,也就是提交該事務(wù)。
- 7. 在收到事務(wù)提交的請求以后,redo log 會把剛才寫入內(nèi)存中的操作記錄寫入到磁盤中,從而完成整個日志的記錄過程。
2Redo log的寫入方式
從上面介紹的Redo log 的執(zhí)行流程中不難看出,redo log在寫入磁盤之前會先將內(nèi)容寫到內(nèi)存中。因此,redo log的寫入包括兩部分內(nèi)容:一部分是內(nèi)存中的日志緩沖,稱作redo log buffer;另一部分是磁盤日志文件,稱作 redo log file。MySQL每執(zhí)行一條DML語句,先將更新記錄寫入redo log buffer ,然后再寫入redo log file。我們將這種先寫日志,再寫磁盤的方式稱為 WAL(Write-Ahead Logging)技術(shù)。
如圖2所示:
圖2 redo log 寫入方式
順著箭頭的方向從左往右看,日志最開始會寫入位于存儲引擎Innodb的redo log buffer中,這個也就是所謂的用戶空間(user space),然后再將日志保存到操作系統(tǒng)內(nèi)核空間(kernel space)的緩沖區(qū)(OS buffer)中。
最后,再從OS buffer寫入到磁盤上的redo log file中,完成寫入操作,這個寫入磁盤的操作也稱作“刷盤”。
了解了redo log的寫入方式之后,我們發(fā)現(xiàn)主要完成的操作是redo log buffer 到磁盤的redo log file的寫入過程,其中需要經(jīng)過OS buffer進(jìn)行中轉(zhuǎn)。關(guān)于redo log buffer寫入redo log file的時機(jī),可以通過 參數(shù)innodb_flush_log_at_trx_commit 進(jìn)行配置,各參數(shù)值含義如下:
參數(shù)為0的時候,稱為“延遲寫”。事務(wù)提交時不會將redo log buffer中日志寫入到OS buffer,而是每秒寫入OS buffer并調(diào)用寫入到redo log file中。換句話說,這種方式每秒會發(fā)起寫入磁盤的操作,假設(shè)系統(tǒng)崩潰,只會丟失1秒鐘的數(shù)據(jù)。
參數(shù)為1 的時候,稱為“實時寫,實時刷”。事務(wù)每次提交都會將redo log buffer中的日志寫入OS buffer并保存到redo log file中。其有點(diǎn)是,即使系統(tǒng)崩潰也不會丟失任何數(shù)據(jù),缺點(diǎn)也很明顯就是每次事務(wù)提交都要進(jìn)行磁盤操作,性能較差。
參數(shù)為2的時候,稱為“實時寫,延遲刷”。每次事務(wù)提交寫入到OS buffer,然后是每秒將日志寫入到redo log file。這樣性能會好點(diǎn),缺點(diǎn)是在系統(tǒng)崩潰的時候會丟失1秒中的事務(wù)數(shù)據(jù)。
3.Redo log記錄形式
redo log是通過循環(huán)寫入的方式保存的。
如圖3所示:
圖3 redo log 循環(huán)寫入(素材來源于互聯(lián)網(wǎng))
redo log buffer(內(nèi)存中)是由首尾相連的四個文件組成的,它們分別是:ib_logfile_1、ib_logfile_2、ib_logfile_3、ib_logfile_4。
寫入的方式也是從文件的頭部開始寫入(假設(shè)),每增加一條日志記錄就往文件的尾部添加,直到把四個文件寫滿,再回到文件開頭的地方(ib_logfile_1)繼續(xù)寫,繼續(xù)寫的時候會覆蓋之前的記錄。
圖3中write pos表示當(dāng)前寫入記錄位置(寫入磁盤的數(shù)據(jù)頁的邏輯序列位置),check point表示刷盤(寫入磁盤)后對應(yīng)的位置。write pos到check point之間的部分用來記錄新日志,也就是留給新記錄的空間。check point到write pos之間是待刷盤的記錄,如果不刷盤會被新記錄覆蓋。
當(dāng)write pos指針追上check point的時候(也就是新記錄即將覆蓋老記錄的時刻),會推動check point向前移動,也就是催促其將記錄刷到磁盤中,這樣好空出位置給新記錄。
當(dāng)redo log buffer根據(jù)check pint刷盤以后,針對Innodb引擎而言是以頁為單位進(jìn)行磁盤存儲,一個事務(wù)可能一個或者多個數(shù)據(jù)頁,每個頁面修改多個字節(jié)。當(dāng)重新啟動Innodb存儲引擎的時候,是會進(jìn)行恢復(fù)操作。因為redo log記錄的是數(shù)據(jù)頁的物理變化,恢復(fù)的速度比邏輯日志(binlog)要快。
在重啟Innodb時,首先會檢查磁盤中數(shù)據(jù)頁的邏輯序列位置,如果數(shù)據(jù)頁的邏輯序列位置小于日志中的位置,則會從check point開始恢復(fù)。如果宕機(jī)的時候,正處于check point的刷盤過程中,且數(shù)據(jù)頁的刷盤進(jìn)度超過了日志頁的刷盤進(jìn)度,此時會出現(xiàn)數(shù)據(jù)頁中記錄的邏輯序列位置大于日志中的邏輯序列位置,這時超出日志進(jìn)度的部分將不會重做,因為這本身就表示已經(jīng)做過的事情,無需再重做。
Binlog
4.Binlog 解決了什么問題?
對于MySQL數(shù)據(jù)庫而言增加數(shù)據(jù)的可靠性是一個永恒的話題,其中主從復(fù)制和數(shù)據(jù)恢復(fù)就是增強(qiáng)數(shù)據(jù)可靠性的兩個重要功能。Binlog就是為實現(xiàn)這兩個功能而設(shè)置的。
主從復(fù)制的場景中在Master 端會開啟binlog ,然后將 binlog 發(fā)送到各個Slave 端,Slave 端重放binlog 從而達(dá)到Slave 端的數(shù)據(jù)和Master端的數(shù)據(jù)保持一致。在數(shù)據(jù)恢復(fù)場景,通過使用mysqlbinlog 工具以及對應(yīng)的binlog 將數(shù)據(jù)恢復(fù)到指定的時間點(diǎn)。那么可以把binlog 解決的問題總結(jié)為兩點(diǎn),就是主從復(fù)制和數(shù)據(jù)恢復(fù)。
5.Binlog的日志格式
從記錄方式上來看binlog通過追加的方式記錄,當(dāng)日志文件尺寸大于給定值后,后續(xù)的日志會記錄到新的文件上。這個與 redo log 的循環(huán)記錄產(chǎn)生鮮明的對比,同時binlog 可通過配置參數(shù) max_binlog_size 設(shè)置每個binlog 文件的大小。
從日志格式來看,Binlog 日志有三種格式,分別為 STATMENT 、 ROW 和 MIXED 。
在 MySQL 5.7.7 之前,默認(rèn)的格式是 STATEMENT , MySQL 5.7.7 之后,默認(rèn)值是 ROW 。日志格式通過 binlog-format 指定。三種格式的定義和優(yōu)缺點(diǎn)如下:
lSTATEMENT:基于SQL語句的復(fù)制(statement-based replication, SBR),記錄的是修改的SQL語句。
n 優(yōu)點(diǎn):由于不用記錄每行日志的更改,因此日志文件小,減少了日志量,節(jié)約了IO,提高了性能;
n 缺點(diǎn):準(zhǔn)確性差,對一些系統(tǒng)行數(shù)不能準(zhǔn)確復(fù)制,例如:now()、uuid()。
lROW:基于行的復(fù)制(row-based replication, RBR),不記錄每條SQL語句的上下文信息,只記錄每行實際數(shù)據(jù)的變更 。
n 優(yōu)點(diǎn): 準(zhǔn)確性強(qiáng),能夠準(zhǔn)確復(fù)制數(shù)據(jù)的變更。
n 缺點(diǎn): 產(chǎn)生的日志文件較大,從造成較大的網(wǎng)絡(luò)IO和磁盤IO。尤其是alter table的時候會讓日志暴漲。
lMIXED:基于STATMENT和ROW兩種模式的混合復(fù)制( mixed-based replication, MBR ),默認(rèn)使用STATEMENT模式保存,STATEMENT模式無法復(fù)制的操作使用ROW模式。
n 優(yōu)點(diǎn):準(zhǔn)確性強(qiáng)、文件大小適中。
n 缺點(diǎn):有可能發(fā)生主從不一致的現(xiàn)象。
在MySQL中可以通過“show binlog events” 命令查看binlog日志的事件。如代碼段1 所示,這里通過上述命令查看“mysql-bin.000002”文件中的binlog 日志情況。
- mysql> show binlog events in 'mysql-bin.000002';
代碼段1
如圖4所示:
圖4 顯示binlog 日志內(nèi)容
通過上述命令展示對應(yīng)binlog 日志事件,從左到右展示如下:
- Log_name:描述存放binlog日志的文件名字。
- Pos:描述記日志的開始位置。
- Event_type:描述時間類型,例如:查詢、插入等。
- Server_id:對應(yīng)數(shù)據(jù)庫服務(wù)器的ID。
- End_log_pos:日志結(jié)束的位置。
- Info:執(zhí)行的SQL語句。
上面是查看日志的事件,這里也可以通過mysqlbinlog命令可以查看binlog的內(nèi)容。如代碼段2 所示,通過mysqlbinlog 命令查看mysql-bin.000002的內(nèi)容。
- mysql> mysqlbinlog 'mysql-bin.000002';
代碼段2
如圖5所示:
圖5 binlog 日志的內(nèi)容
我們將上述查看命令返回的結(jié)果截取其中一部分給大家講解,我們從上往下看:
- “at 294”說明“事件”的起點(diǎn),也就是從文件的第294字節(jié)開始。
- “120330 17:54:46”表示事件發(fā)生的時間戳信息。
- “end_log_pos 388 ”表示日志記錄結(jié)束的字節(jié)位置,也就是在文件的第388 字節(jié)截止。
- "exec_time=28",表示事件執(zhí)行花費(fèi)的時間。
- “error_code=0”,表示錯誤代碼為0,也就是沒有錯誤。
- “server id 1”,表示服務(wù)器的標(biāo)識id。
需要注意的是binlog的事務(wù)提交,是一次性將事務(wù)進(jìn)行提交(一個事物包含一個或者多個SQL語句)。而redo log可以在事務(wù)開始之后就開始逐步寫入磁盤。因此對于事務(wù)的提交,即便是較大的事務(wù),提交(commit)都是很快的,但是在開啟了binlog的情況下,對于較大事務(wù)的提交,可能會變得比較慢。因為binlog事務(wù)提交是一次性寫入。
6.Redo log與Binlog區(qū)別與合作
前面介紹了redo log 和 binlog,那么這里總結(jié)一下它們之間的區(qū)別如下表格。
由 binlog 和 redo log 的區(qū)別可知:binlog 日志只用于歸檔,但僅僅依靠 binlog 是沒有 crash-safe 能力的。但只有 redo log 也不行,因為 redo log 是 InnoDB 特有的,且日志記錄落盤后會被覆蓋掉。因此需要 binlog 和 redo log 二者同時記錄,才能保證當(dāng)數(shù)據(jù)庫發(fā)生宕機(jī)重啟時,數(shù)據(jù)不會丟失。
那么如何讓兩個日志保持一致呢?
如圖5所示:
圖5 redo log 和 binlog 事務(wù)保持一致
該圖沿用了圖1 的例子,稍微不同的是加入了一個步驟??吹骄G色虛線框的部分加入了寫入binlog的步驟。當(dāng)事務(wù)為prepare狀態(tài)的時候,在commit事務(wù)之間,會先將日志保存到binlog當(dāng)中,然后再提交給 Innodb中的redo log,最后完成commit的操作。
再聚焦于redo log 和 binlog 在提交成功和失敗兩種情況中的狀態(tài)變化。
如圖6 所示:
圖6 redo log 和 binlog 狀態(tài)變遷圖(素材來源于互聯(lián)網(wǎng))
從上往下看,先看紅色線條的部分,當(dāng)寫入redo log并且事務(wù)狀態(tài)為prepare的時候,如果寫入成功直接寫入binlog,如果binlog 寫入也成功,redo log 狀態(tài)設(shè)置為commit。如果寫入binlog的時候失敗了,沿著紅色箭頭向上回滾此次事務(wù)。再回到最上面,看綠色箭頭的部分,如果寫入redo log 狀態(tài)為prepare 此時寫入失敗,不再寫入binlog,事務(wù)直接回滾。
可以看出這里為了保持兩個日志的一致性,使用了兩段提交。redo log和binlog是兩個獨(dú)立的邏輯,如果不用兩階段提交,要么就是先寫完redo log再寫 binlog,或者先寫binlog再寫 redo log。看看這兩種方式會有什么問題:
- 先寫redo log后寫binlog。假設(shè)在redo log寫完,binlog還沒有寫完的時候,也就是說binlog 沒有事務(wù)中更新的語句。此時MySQL重啟并使用binlog來恢復(fù)數(shù)據(jù),由于之前更新的語句沒有保存數(shù)據(jù)庫就會少了一次更新,導(dǎo)致數(shù)據(jù)的不一致。
- 先寫binlog后寫redo log。如果在binlog寫完之后服務(wù)器宕機(jī)了,由于redo log還沒寫,也就是數(shù)據(jù)還沒有寫到數(shù)據(jù)庫中。但是binlog里面已經(jīng)記錄了,意思是把本來不應(yīng)該更新的數(shù)據(jù)記錄到更新里面了。此時MySQL數(shù)據(jù)庫重啟,就會把這條不該更新的數(shù)據(jù)更新到數(shù)據(jù)庫中,導(dǎo)致數(shù)據(jù)的不一致。
總結(jié)
本文通過提高M(jìn)ySQL可靠性入手,分別介紹redo log 和binlog的實現(xiàn)機(jī)制,再合并講解兩者的區(qū)別與合作。Redo log部分首先提出redo log 的目的是解決事務(wù)提交的持久性,作為Innodb存儲引擎特有的日志模式redo log 的執(zhí)行使用了兩步提交。在寫入方式上面會先從redo log buffer 寫入到OS buffer,最后寫盤到 redo log file中。
其對應(yīng)了三種寫入配置,分別是延遲寫、實時寫實時刷和實時寫延遲刷。在記錄形式方面由四個文件循環(huán)寫入的方式進(jìn)行,通過write pos 和check point 推進(jìn)寫入的進(jìn)度。轉(zhuǎn)眼看binlog,它是為了主從復(fù)制和數(shù)據(jù)恢復(fù)而生的。有三種日志格式分別是STATEMENT、ROW、MIXED。
日志內(nèi)容主要記錄了開始的記錄點(diǎn)、結(jié)束的記錄點(diǎn)、記錄發(fā)生的時間戳、記錄花費(fèi)的時間以及具體執(zhí)行的事件操作。在介紹完兩個日志以后對其區(qū)別進(jìn)行了闡述,并且提出兩者結(jié)合可以保證數(shù)據(jù)庫宕機(jī)時,數(shù)據(jù)不丟失。介紹二者在事務(wù)處理流程中是如何合作的,并且列舉了兩個場景描述了redo log 和binlog 進(jìn)行兩段提交的必要性。