MySQL是常用的資料庫存儲應用,我們利用它存儲資訊、查詢資訊、處理事務。特别是為了提高可用性會用到事務一緻性、主從複制、資料恢複等功能。我們在使用這些功能的時候,是否想過其背後有哪些原理和機制在支撐?今天我們聚焦redo log和binlog兩個MySQL的日志機制,以及它們是如何配合提高MySQL存儲可靠性的。今天會學到以下内容:
Redo log
- Redo log 解決了什麼問題?
- Redo log的執行流程
- Redo log的寫入方式
- Redo log記錄形式
Binlog
- Binlog解決了什麼問題?
- Binlog的日志格式
- Redo log 與Binlog的差別與合作
MySQL應用中處理事務是一個重要的任務,而在事務處理的四個特性中(ACID),存在一個持久性(Durability),它表示在事務執行過程中,對資料的所有改動都必須在事務成功結束前儲存至某種實體儲存設備中。
換句話說,隻要事務送出成功,那麼對資料庫做的修改就被永久儲存下來了,不可能因為任何原因再回到原來的狀态。那麼為什麼要在MySQL中考慮事務持久性的問題呢?假設這麼一種場景,當資料存儲的事務正在執行但是資料還沒有儲存的時候,資料庫當機了,那麼這些沒來得及存儲到磁盤的資料就丢失了,如果此時有一種機制能夠記錄這個事務的操作,當資料庫服務恢複的時候,運作記錄的操作那麼這些沒有來得及存儲的資料就能夠正确儲存了。Redo log 就是通過這種手段來實作事務持久性的。上面的場景中是資料庫伺服器當機,如果發生其他故障導緻尚有髒頁未寫入磁盤的場景,也是可以通過Redo log恢複的。
了解了為什麼使用redo log 以後再來看看其執行流程,如圖1 所示,該泳道圖由MySQL用戶端、MySQL Server 層和MySQL 存儲引擎層組成。由于redo log是在Innodb存儲引擎中使用的,這裡假設存儲引擎就是Innodb。由于MySQL Server 層主要負責SQL語句的分析、優化和執行工作,而MySQL存儲引擎層主要負責存儲工作,redo log 也運作在這一層。 跟随圖中的序号來看看redo log 的運作流程。
1.
從MySQL用戶端請求語句“update T set a=1 where id=2”,并發現MySQL Server 層。
2.
接收到SQL請求以後MySQL Server 層會對其進行分析、優化、執行等處理工作,将生成的SQL執行計劃發到存儲引擎層執行。
3.
存儲引擎層将“a修改為1”的這個操作記錄到記憶體中。
4.
記錄到記憶體以後會修改redo log 的記錄,會在添加一行記錄,其内容是“需要在哪個資料頁上做什麼修改”。
5.
此後,将事務的狀态設定為prepare ,說明已經準備好送出事務了。
6.
等到MySQL Server 層處理完事務以後,會将事務的狀态設定為commit,也就是送出該事務。
7.
在收到事務送出的請求以後,redo log 會把剛才寫入記憶體中的操作記錄寫入到磁盤中,進而完成整個日志的記錄過程。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwATMfRHLGZkRGZkRfJ3bs92YskmNhVTYykVNQJVMRhXVEF1X0hXZ0xCNx8VZ6l2cssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLzY2YykjZmFzM4M2NhJ2MxYjNwADMzQjY4cDNwEWN5QGOmFjYxI2LcBTMxIDMy8CXzV2Zh1WavwVbvNmLvR3YxUjL4M3Lc9CX6MHc0RHaiojIsJye.png)
圖1 redo log執行流程
從上面介紹的Redo log 的執行流程中不難看出,redo log在寫入磁盤之前會先将内容寫到記憶體中。是以,redo log的寫入包括兩部分内容:一部分是記憶體中的日志緩沖,稱作redo logbuffer;另一部分是磁盤日志檔案,稱作 redo log file。 MySQL每執行一條DML語句,先将更新記錄寫入redo log buffer ,然後再寫入redo log file。我們将這種先寫日志,再寫磁盤的方式稱為 WAL(Write-Ahead Logging)技術。
如圖2所示,順着箭頭的方向從左往右看,日志最開始會寫入位于存儲引擎Innodb的redo log buffer中,這個也就是所謂的使用者空間(user space),然後再将日志儲存到作業系統核心空間(kernel space)的緩沖區(OS buffer)中。最後,再從OS buffer寫入到磁盤上的redo log file中,完成寫入操作,這個寫入磁盤的操作也稱作“刷盤”。
圖2 redo log 寫入方式
了解了redo log的寫入方式之後,我們發現主要完成的操作是redo log buffer 到磁盤的redo log file的寫入過程,其中需要經過OS buffer進行中轉。關于redo log buffer寫入redo log file的時機,可以通過參數innodb_flush_log_at_trx_commit 進行配置,各參數值含義如下:
- 參數為0的時候,稱為“延遲寫”。事務送出時不會将redo log buffer日志寫入到OS buffer,而是每秒寫入OS buffer并調用寫入到redo log file中。換句話說,這種方式每秒會發起寫入磁盤的操作,假設系統崩潰,隻會丢失1秒鐘的資料。
- 參數為1 的時候,稱為“實時寫,實時刷”。事務每次送出都會将redo log buffer中的日志寫入OS buffer并儲存到redo log file中。其有點是,即使系統崩潰也不會丢失任何資料,缺點也很明顯就是每次事務送出都要進行磁盤操作,性能較差。
- 參數為2的時候,稱為“實時寫,延遲刷”。每次事務送出寫入到OS buffer,然後是每秒将日志寫入到redo log file。這樣性能會好點,缺點是在系統崩潰的時候會丢失1秒中的事務資料。
redolog
是通過循環寫入的方式儲存的,如圖3所示,redo log buffer(記憶體中)是由首尾相連的四個檔案組成的,它們分别是:ib_logfile_1、ib_logfile_2、ib_logfile_3、ib_logfile_4。寫入的方式也是從檔案的頭部開始寫入(假設),每增加一條日志記錄就往檔案的尾部添加,直到把四個檔案寫滿,再回到檔案開頭的地方(ib_logfile_1)繼續寫,繼續寫的時候會覆寫之前的記錄。圖3中write pos表示目前寫入記錄位置(寫入磁盤的資料頁的邏輯序列位置),checkpoint表示刷盤(寫入磁盤)後對應的位置。write pos到check point之間的部分用來記錄新日志,也就是留給新記錄的空間。check
point到write pos之間是待刷盤的記錄,如果不刷盤會被新記錄覆寫。當write pos指針追上check point的時候(也就是新記錄即将覆寫老記錄的時刻),會推動check point向前移動,也就是催促其将記錄刷到磁盤中,這樣好空出位置給新記錄。
圖3 redo log 循環寫入(素材來源于網際網路)
當redo log buffer根據check pint刷盤以後,針對Innodb引擎而言是以頁為機關進行磁盤存儲,一個事務可能一個或者多個資料頁,每個頁面修改多個位元組。當重新啟動Innodb存儲引擎的時候,是會進行恢複操作。因為redo log記錄的是資料頁的實體變化,恢複的速度比邏輯日志(binlog)要快。
在重新開機Innodb時,首先會檢查磁盤中資料頁的邏輯序列位置,如果資料頁的邏輯序列位置小于日志中的位置,則會從check point開始恢複。 如果當機的時候,正處于check point的刷盤過程中,且資料頁的刷盤進度超過了日志頁的刷盤進度,此時會出現資料頁中記錄的邏輯序列位置大于日志中的邏輯序列位置,這時超出日志進度的部分将不會重做,因為這本身就表示已經做過的事情,無需再重做。
Binlog 解決了什麼問題?
對于MySQL資料庫而言增加資料的可靠性是一個永恒的話題,其中主從複制和資料恢複就是增強資料可靠性的兩個重要功能。Binlog就是為實作這兩個功能而設定的。主從複制的場景中在Master 端會開啟binlog ,然後将 binlog 發送到各個Slave 端,Slave 端重放binlog 進而達到Slave 端的資料和Master端的資料保持一緻。在資料恢複場景,通過使用mysqlbinlog 工具以及對應的binlog 将資料恢複到指定的時間點。那麼可以把binlog 解決的問題總結為兩點,就是主從複制和資料恢複。
從記錄方式上來看binlog通過追加的方式記錄,當日志檔案尺寸大于給定值後,後續的日志會記錄到新的檔案上。這個與 redo log 的循環記錄産生鮮明的對比,同時binlog 可通過配置參數 max_binlog_size 設定每個binlog 檔案的大小。
從日志格式來看,Binlog 日志有三種格式,分别為 STATMENT 、 ROW 和 MIXED 。
在 MySQL 5.7.7 之前,預設的格式是 STATEMENT , MySQL 5.7.7 之後,預設值是 ROW 。日志格式通過 binlog-format 指定。三種格式的定義和優缺點如下:
STATEMENT:基于SQL語句的複制(statement-based replication, SBR),記錄的是修改的SQL語句。
- 優點:由于不用記錄每行日志的更改,是以日志檔案小,減少了日志量,節約了IO,提高了性能;
- 缺點:準确性差,對一些系統行數不能準确複制,例如:now()、uuid()。
ROW:基于行的複制(row-basedreplication, RBR),不記錄每條SQL語句的上下文資訊,隻記錄每行實際資料的變更 。
- 優點: 準确性強,能夠準确複制資料的變更。
- 缺點: 産生的日志檔案較大,從造成較大的網絡IO和磁盤IO。尤其是alter table的時候會讓日志暴漲。
MIXED:基于STATMENT和ROW兩種模式的混合複制( mixed-based replication,MBR ),預設使用STATEMENT模式儲存,STATEMENT模式無法複制的操作使用ROW模式。
- 優點:準确性強、檔案大小适中。
- 缺點:有可能發生主從不一緻的現象。
在MySQL中可以通過“show binlog events” 指令檢視binlog日志的事件。如代碼段1 所示,這裡通過上述指令檢視“mysql-bin.000002”檔案中的binlog 日志情況。
mysql> show binlog events in 'mysql-bin.000002'; |
代碼段1
如圖4所示,通過上述指令展示對應binlog 日志事件,從左到右展示如下:
- Log_name:描述存放binlog日志的檔案名字。
- Pos:描述記日志的開始位置。
- Event_type:描述時間類型,例如:查詢、插入等。
- Server_id:對應資料庫伺服器的ID。
- End_log_pos:日志結束的位置。
- Info:執行的SQL語句。
圖4 顯示binlog 日志内容
上面是檢視日志的事件,這裡也可以通過mysqlbinlog指令可以檢視binlog的内容。如代碼段2 所示,通過mysqlbinlog 指令檢視mysql-bin.000002的内容。
mysql> mysqlbinlog 'mysql-bin.000002'; |
代碼段2
如圖5所示,我們将上述檢視指令傳回的結果截取其中一部分給大家講解,我們從上往下看:
- “at 294”說明“事件”的起點,也就是從檔案的第294位元組開始。
- “120330 17:54:46”表示事件發生的時間戳資訊。
- “end_log_pos 388 ”表示日志記錄結束的位元組位置,也就是在檔案的第388 位元組截止。
- "exec_time=28",表示事件執行花費的時間。
- “error_code=0”,表示錯誤代碼為0,也就是沒有錯誤。
- “server id 1”,表示伺服器的辨別id。
圖5 binlog 日志的内容
需要注意的是binlog的事務送出,是一次性将事務進行送出(一個事物包含一個或者多個SQL語句)。而redo log可以在事務開始之後就開始逐漸寫入磁盤。是以對于事務的送出,即便是較大的事務,送出(commit)都是很快的,但是在開啟了binlog的情況下,對于較大事務的送出,可能會變得比較慢。因為binlog事務送出是一次性寫入。
Redo log與Binlog差別與合作
前面介紹了redo log 和 binlog,那麼這裡總結一下它們之間的差別如下表格。
Redo log | ||
适用場景 | 适用于崩潰恢複(crash-safe)。 | 适用于主從複制和資料恢複。 |
實作方式 | InnoDB 引擎層實作的,并不是所有引擎都有。 | Server 層實作的,所有引擎都可以使用 binlog 日志。 |
記錄方式 | redo log 采用循環寫的方式記錄,當寫到結尾時,會回到開頭循環寫日志。 | Binlog通過追加的方式記錄,當檔案尺寸大于給配置值後,後續的日志會記錄到新的檔案上。 |
檔案大小 | redo log 的大小是固定的。 | Binl og 可通過配置參數 max_binlog_size 設定每個binlog 檔案的大小。 |
由 binlog 和 redo log 的差別可知: binlog 日志隻用于歸檔,但僅僅依靠 binlog 是沒有 crash-safe 能力的。但隻有 redo log 也不行,因為 redo log 是 InnoDB 特有的,且日志記錄落盤後會被覆寫掉。是以需要 binlog 和 redo log 二者同時記錄,才能保證當資料庫發生當機重新開機時,資料不會丢失。
那麼如何讓兩個日志保持一緻呢?如圖6所示,該圖沿用了圖1 的例子,稍微不同的是加入了一個步驟。看到綠色虛線框的部分加入了寫入binlog的步驟。當事務為prepare狀态的時候,在commit事務之間,會先将日志儲存到binlog當中,然後再送出給 Innodb中的redo log,最後完成commit的操作。
圖6 redo log 和 binlog 事務保持一緻
再聚焦于redo log 和 binlog 在送出成功和失敗兩種情況中的狀态變化。如圖7 所示,從上往下看,先看紅色線條的部分,當寫入redo log并且事務狀态為prepare的時候,如果寫入成功直接寫入binlog,如果binlog 寫入也成功,redo log 狀态設定為commit。如果寫入binlog的時候失敗了,沿着紅色箭頭向上復原此次事務。再回到最上面,看綠色箭頭的部分,如果寫入redo log 狀态為prepare 此時寫入失敗,不再寫入binlog,事務直接復原。
圖7 redo log 和 binlog 狀态變遷圖(素材來源于網際網路)
可以看出這裡為了保持兩個日志的一緻性,使用了兩段送出。redo log和binlog是兩個獨立的邏輯,如果不用兩階段送出,要麼就是先寫完redo log再寫 binlog,或者先寫binlog再寫 redo log。看看這兩種方式會有什麼問題:
- 先寫redo log後寫binlog。假設在redo log寫完,binlog還沒有寫完的時候,也就是說binlog 沒有事務中更新的語句。此時MySQL重新開機并使用binlog來恢複資料,由于之前更新的語句沒有儲存資料庫就會少了一次更新,導緻資料的不一緻。
- 先寫binlog後寫redo log。如果在binlog寫完之後伺服器當機了,由于redo log還沒寫,也就是資料還沒有寫到資料庫中。但是binlog裡面已經記錄了,意思是把本來不應該更新的資料記錄到更新裡面了。此時MySQL資料庫重新開機,就會把這條不該更新的資料更新到資料庫中,導緻資料的不一緻。
總結