結合 MySQL 更新流程看 undolog、redolog、binlog

開篇 tip

我們知道日誌的作用不言而喻,無論是線上排查,亦或是性能優化,幾乎都需要從日誌中來獲得信息作爲依據,而 MySQL 中,很多很多的功能也都需要基於日誌實現,比如事務回滾、數據持久化、數據恢復、數據遷移、MVCC 機制.....

我們在瞭解具體內容之前,先對三種日誌之間的小總結,涉及到使用場景和文件等,可以把這個當做先前總結,更細的內容在每一章會提及。

簡單梳理下日誌是在哪個地方寫入到不同日誌文件中的,undolog、redolog 都是 InnoDB 引擎中的日誌,而且都是在 Buffer Pool 中,而 binlog 在 Server 層中,位於每條線程中,並且每種日誌在磁盤中的的歸檔方式和文件都是不一樣的,之間的區別如下圖。

WAL 機制是什麼?

WAL,全稱是 Write-Ahead Logging, 預寫日誌系統。指的是 MySQL 的寫操作並不是立刻更新到磁盤上,而是先記錄在日誌上,然後在合適的時間再更新到磁盤上。

MySQL 真正使用 WAL 的原因是:磁盤的寫操作是隨機 IO,比較耗性能,所以如果把每一次的更新操作都先寫入 log 中,那麼就成了順序寫操作,實際更新操作由後臺線程再根據 log 異步寫入。

這樣對於 client 端,延遲就降低了。並且,由於順序寫入大概率是在一個磁盤塊內,這樣產生的 IO 次數也大大降低。所以 WAL 的核心在於將隨機寫轉變爲了順序寫,降低了客戶端的延遲,提升了吞吐量。

CheckPoint 技術是啥?

在 MySQL 中主要是將緩衝池中的髒頁刷回到磁盤。InnoDB 存儲引擎內部,兩種 checkpoint,分別爲: Sharp Checkpoint 和 Fuzzy Checkpoint。

而 MYSQL 中主要是爲了保證數據會做很多 CheckPoint 動作

Undo log

undo log 叫做回滾日誌,它保證了事務的 ACID 特性中的原子性(Atomicity),是引擎層生成的日誌,記錄的是邏輯操作,用於記錄數據被修改前的信息,這裏的邏輯操作是指:"增(insert)刪(delete)改(update)"。

undo log 的兩個主要作用是【事務回滾】 和 通過 ReadView + undo log 實現 【MVCC (多版本併發控制)】

記錄內容

不同的 SQL 修改操作,記錄的 undo log 分別是什麼呢?

insert 插入操作,會在 undo log 中記錄本次插入的主鍵 id,等事務回滾時,會 delete 此主鍵對應的記錄,

update 更新操作,會記錄一條相反的 update 的 undo log,回滾時執行一次相反 update,更新回原來的數據,

delete 刪除操作,會記錄刪除前的數據,回滾時,insert 原來的數據。

通過上面的相反邏輯處理,這樣的話即使發生錯誤時,就能回滾到事務之前的數據狀態。

一條 SQL 沒有 begin 開啓事務和 Commit 提交事務,也能自己提交事務?

是的,MYSQL 事務分爲【隱式事務和顯示事務】

隱式事務:

比如 insert、update、delete 語句,事務的開啓、提交或回滾由 mysql 內部自動控制的,事務自動開啓、提交或回滾。

我們可以通過 show variables like 'autocommit'查看是否開啓了自動提交,autocommit 爲 ON 表示開啓了自動提交

顯示事務:

顯式事務是指在應用程序中明確指定事務的開始和結束,使用 BEGIN、COMMIT 和 ROLLBACK 語句來控制事務的執行,語法如下:

BEGIN;
-- SQL statements
COMMIT;

事務回滾

一條記錄的每一次更新操作產生的 undo log 格式都有一個 roll_pointer 指針和一個 trx_id 事務 id,如下圖:

而事務發生回滾,會讀取 undo log 裏的數據,本質上並不會以執行反 SQL 的模式還原數據,而是直接將 roll_ptr 回滾指針指向的 Undo 記錄。

來看 insert 和 update 產生的日誌

插入的數據都會生成一條 insert undo log,並且數據的回滾指針會指向它。undo log 會記錄 undo log 的序號、插入主鍵的列和值…

MVCC

MVCC 是通過 ReadView + undo log 實現的。undo log 爲每條記錄保存多份歷史數據,MySQL 在執行快照讀(普通 select 語句)的時候,會根據事務的 Read View 裏的信息,順着 undo log 的版本鏈找到滿足其可見性的記錄。

具體的實現原理咱們在以後的分享中繼續說

Redo log

在 InnoDB 存儲引擎中,大部分 redo log 記錄的是物理日誌,記錄的是某個數據頁做了什麼修改。

爲什麼說大部分是物理日誌呢?

因爲 redo log 日誌主要包括兩部分:

一是在內存中重做日誌緩衝(redo log Buffer)易丟失,在內存中, 二是重做日誌文件(redo log file),保存在磁盤中。

爲何需要 redo log

我們知道 buffer pool 確實提高了讀寫效率沒錯,但是問題來了,Buffer Pool 是基於內存的,而內存總是不可靠,萬一斷電重啓,還沒來得及落盤的髒頁數據就會丟失。

也正由於該原因,redo log 應運而生!

因爲 redo log 就是來解決這個問題的,它是搭配 buffer pool 緩衝池、change buffer 使用的,作用就是持久化記錄的寫操作,防止在寫操作更新到磁盤前發生斷電丟失這些寫操作,直到該操作對應的髒頁真正落盤。

知識充電站:緩衝池 buffer pool 的作用是緩存磁盤上的數據頁,減少磁盤的 IO;change buffer 的作用是將寫操作先存在內存中,等到下次需要讀取這些操作涉及到的數據頁時,就把數據頁加載到緩衝池中,然後在緩衝池中更新,然後由後臺線程再刷寫到磁盤。

總結起來就是【持久化防止斷電數據丟失】,從而保證了事務四大特性中的持久性。

和 undolog 的區別

這裏我們可以看出 redo log 和 undo log 之間的一些明顯區別,文章開頭我們也總結過,這裏再回顧下更清晰!

兩種日誌是屬於 InnoDB 存儲引擎的日誌,主要區別在於

寫入流程

這裏寫入 redo log buffer 就用到了開頭提到的 WAL(Write-Ahead Logging)技術,日誌先寫入 redo log buffer 緩衝區,然後被後臺進程刷盤。而不用等髒頁刷入磁盤,通過先將 redo log 持久化到磁盤中,即使系統奔潰,髒頁刷盤失敗,也可以通過 redo log 的內容,將數據恢復到當前最新的狀態。

checkpoint

我們知道 redo log 默認情況下存儲在 data 目錄下 ib_logfile0 和 ib-logfile1 兩個文件中,其實可以通過 innodb_log_file_size 設置大小, innodb_log_files_in_group 設置文件個數。比如可以配置爲一組 4 個文件 ib_logfile0 到 ib_logfile3,每個文件的大小是 1GB,整個 redo log 日誌文件組可以記錄 4G 的內容。

爲什麼默認情況下 redo log 由 ib_logfile0 和 ib-logfile1 兩個日誌文件?

主要是 MySQL 通過來回寫這兩個文件的形式記錄 redo log 日誌,用兩個日誌文件組成一個 “環形”,如下圖:

redo log 是採用循環寫的方式,圖中各部分代表的意思如下:

紅色和藍色部分如何理解呢?

write pos 和 check point 兩根指針中間區域,也就是圖中的紅色區域,代表是可以寫入日誌記錄的可用空間,而藍色區域則表示日誌落盤但數據還未落盤的記錄。

舉個例子:當一個事務寫了 redo log 日誌、並將數據寫入緩衝區後,但數據還未寫入磁盤文件中,此時這個事務對應的 redo log 記錄就爲上圖中的藍色,而當一個事務所寫的數據也落盤後,對應的 redo log 記錄就會變爲紅色

write pos 指針追上 check point 指針怎麼辦

如果 write pos 指針追上 check point 指針,那麼環中的紅色區域就沒了,也就意味着無法再寫入 redo log 日誌了,因爲文件滿了,再執行寫入操作就會阻塞 MYSQL。

此時會觸發 checkpoint 刷盤機制,將 Buffer Pool 中的髒頁刷新到磁盤中,然後標記 redo log 哪些記錄可以被擦除,接着對舊的 redo log 記錄進行擦除,等擦除完舊記錄騰出了空間,checkpoint 就會往後移動,紅色區域也會不斷增長,因此阻塞的寫事務才能繼續執行。

刷盤時機和策略

緩存在 redo log buffer 裏的 redo log 是在內存中的,最終是要刷到磁盤中,下面⑤種場景會刷新到磁盤中:

log buffer 空間不足時:log buffer 的大小是有限的,如果當前寫入 log buffer 的 redo 日誌量已經佔滿了 log buffer 總容量的 50% 左右,就需要將這些日誌刷新到磁盤中。

事務提交時:爲了保持持久性,必須要把頁面修改時所對應的 redo 日誌刷新到磁盤,否則系統崩潰後,無法將該事務對頁面所做的修改恢復過來。

將某個髒頁刷新到磁盤前,會先保證該髒頁對應的 redo 日誌刷新到磁盤中:redo 日誌是順序寫入的,因此在將某個髒頁對應的 redo 日誌從 redo log buffer 刷新到磁盤中時,也會保證將在其之前產生的 redo 日誌也刷新到磁盤中。

後臺線程:大約以每秒一次的頻率將 redo log buffer 中的 redo 日誌刷新到磁盤中。

觸發 checkpoint 時。

什麼是【刷盤策略】,可以理解爲何時以何種方式刷新到真正的 redo log file 中。

InnoDB 通過 innodb_flush_log_at_trx_commit 參數可以控制策略,該參數控制 commit 提交事務時,如何將 redo log buffer 中的日誌刷新到 redo log file 中,它支持設定 0,1, 2 也就是說支持三種策略設置,不同的值代表的意思如下。

如何查看 mysql 默認策略呢

show variables like 'innodb_flush_log_at_trx_commit'
//在不改動的情況下 innodb_flush_log_at_trx_commit值是1

設置爲 0(延遲寫) :每次事務提交時不主動進行刷盤操作,redo log 依然留在 redo log buffer 中,然後每秒寫入 page cache 中,然後持久化到磁盤中

設置爲 1 (實時寫,實時刷):每次事務提交時都將直接講緩存在 redo log buffer 中的 redo log 直接持久化到磁盤中( 默認值 )

設置爲 2(實時寫,延時刷) :表示每次事務提交時都只把 redo log buffer 內容寫入 page cache,不進行同步,由 os 自己決定什麼時候同步到磁盤文件

設置不同的 innodb_flush_log_at_trx_commit 值的時候輸盤策略流程圖如下:

通過流程圖可以知道,設置爲 0 時,commit 事務會把緩存在 redo log buffer 中的 redo log ,然後後臺線程每秒執行一次講 redo log buffer 操作系統的 page cache,並調用 fsync() 持久化到磁盤,即使 MySQL 崩潰只會導致上一秒鐘所有事務數據的丟失。

設置爲 1 時,commit 事務會把緩存在 redo log buffer 中的 redo log 直接持久化到磁盤,這種場景下是不會丟失數據

設置爲 2 時,commit 事務會把緩存在 redo log buffer 中的 redo log 寫入到 page cache,這種場景下是不會丟失數據,然後後臺線程每秒執行一次將 page cache 的內容持久化到磁盤。

崩潰恢復

MySQL 崩潰也是一次關閉過程,只是比正常關閉突然和迅速了一些。正常關閉時,MySQL 會做一系列收尾工作,例如:清理 undo 日誌、合併 change buffer 緩衝區等操作。

在 MySQL 服務器正常的時候 undo log 看起來是個累贅,但是萬一出問題就是個寶,可以在重啓的時候恢復到奔潰錢的狀態,主要通過以下步驟:

**找到 last_checkpoint_lsn:**讀取 Redo 日誌之前,必須先確定一個起點,這個起點就是 InnoDB 最後一次 checkpoint 操作的 lsn,也就是 last_checkpoint_lsn。

修復損壞的數據頁:兩次寫文件中的所有數據頁都加載到內存緩衝區之後,需要用這些頁來把系統表空間、獨立表空間、undo 表空間中損壞的數據頁恢復到正常狀態。

讀取 Redo 日誌:確定了讀取 Redo 日誌的起點 last_checkpoint_lsn,接下來就該讀取 Redo 日誌了

應用 Redo 日誌

想深入瞭解的朋友可以網上自省搜索,這裏就不多說了,主要流程也挺多的,哈哈

Binlog

bin log 主要是記錄所有對數據庫表結構變更和表數據修改的操作,對於 select、show 這類讀操作並不會記錄。

bin log 是在事務提交後再服務層產生的日誌,主要作用有兩個:

數據恢復 :Binlog 詳細記錄了所有修改數據的 SQL,當某一時刻的數據誤操作而導致出問題,或者數據庫宕機數據丟失,那麼可以根據 Binlog 來回放歷史數據。

主從複製:想要做多機備份的業務,可以去監聽當前寫庫的 Binlog 日誌,同步寫庫的所有更改。

格式類型

bin log 日誌有三種格式,分別爲 statement、row 和 mixed。

在 MySQL 5.7.7 之前,默認的格式是 statement,MySQL 5.7.7 之後,默認值是 row。日誌格式通過 binlog-format 指定。

statement 模式:每一條修改數據的 SQL 都會被記錄到 bin log 中。由於只記錄對數據庫產生變更操作的 SQL,日誌不會太大,性能會比較不錯。但是如果在 SQL 中使用了 sysdate()、now() 這類函數, 在恢復數據、主從同步數據時,有時會出現數據不一致的情況 row 模式:不再記錄每條造成變更的 SQL 語句,而是記錄具體哪一個分區中的、哪一個頁中的、哪一行數據被修改了。所以就沒有 statement 模式下動態函數問題,缺點是每行數據變化都會被記錄,bin log 日誌文件會比較大 mixed 模式:statement 和 row 模式的結合版,它會根據不同的情況自動使用 row 模式和 statement 模式

寫入方式

binlog 是通過追加的方式進行寫入的,可以通過 max_binlog_size 參數設置每個 binlog 文件的大小,當文件大小達到給定值之後,會生成新的文件來保存日誌。

每個日誌文件的命名爲 mysql-bin.000001、mysql-bin.000002、mysql-bin.00000x....,可以通過 show binary logs; 命令查看已有的 bin-log 日誌文件。

刷盤時機和策略

事務執行時,會給每個線程在內存中分配一塊地方叫 bin log cache,binlog 文件就記錄在這裏,事務提交的時候,再把 bin log cache 寫到 bin log 文件中。注意,一個事務的 bin log 不能被拆開提交,無論這個事務多大,也要確保一次性寫入,這樣才能保證原子性。而對於 InnoDB 存儲引擎而言,只有在事務提交時纔會記錄 bin log ,此時記錄還在內存中。

我們來看看在內存中的 bin log 何時保存到磁盤中,也就是【刷盤時機】

圖中 fsync,就是將數據持久化到磁盤的操作,而 write 還只是把日誌寫到 page cache 的 bin log 文件中,還沒有持久化到磁盤。

write 和 fsync 的時機和頻率,是由參數 sync_binlog 控制,該參數控制着二進制日誌寫入磁盤的過程,該參數的有效值爲 0 、1、N

從上面可以看出,syncbinlog 最安全的是設置是 1 ,但是將這個參數設爲 1 以上的數值會提高數據庫的性能的損耗,而操作系統默認設置的是 sync_binlog = 0。

主從複製

主從複製主要是依賴 bin log,slave 從庫從 master 主庫讀取 bin log 進行數據同步。

MySQL 主從複製是異步且串行化的 ,也就是說主庫上執行事務操作的線程不會等待複製 binlog 的線程同步完成,流程如下圖:

整個流程其實就是對 bin log 的寫入、同步、重做過程。

主從同步設計當然有好處了,比如**讀寫分離(數據寫入在 master 上,數據讀取在 slave)、數據備份(保留了多份一樣的數據)。**但是如果 slave 增加,從庫連接上來的 I/O 線程較多,那麼就會對主庫資源消耗增大,造成主從延遲。

當然造成主從延遲的原因還有很多咯,網絡問題,帶寬問題,慢 SQL 等,這些就不進行過多說明,朋友們私下去了解吧!

主從複製方式

同步複製:MySQL 主庫提交事務的線程要等待所有從庫的複製成功響應,才返回客戶端結果。這種方式在實際項目中,基本上沒法用,原因有兩個:一是性能很差,因爲要複製到所有節點才返回響應;二是可用性也很差,主庫和所有從庫任何一個數據庫出問題,都會影響業務。

異步複製(默認模型):MySQL 主庫提交事務的線程並不會等待 binlog 同步到各從庫,就返回客戶端結果。這種模式一旦主庫宕機,數據就會發生丟失。

半同步複製:MySQL 5.7 版本之後增加的一種複製方式,介於兩者之間,事務線程不用等待所有的從庫複製成功響應,只要一部分複製成功響應回來就行,比如一主二從的集羣,只要數據成功複製到任意一個從庫上,主庫的事務線程就可以返回給客戶端。這種半同步複製的方式,兼顧了異步複製和同步複製的優點,即使出現主庫宕機,至少還有一個從庫有最新的數據,不存在數據丟失的風險。

爲什麼有了 binlog, 還要有 redo log?

其實 redo log、bin log 都是記錄更新數據庫的操作,爲啥要設計兩個日誌呢,這個問題跟 MySQL 的時間線有關係。

最開始 MySQL 裏並沒有 InnoDB 引擎,MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒有 crash-safe 的能力,binlog 日誌只能用於歸檔。

InnoDB 引擎 是另一個公司以插件形式引入 MySQL 的,而 MYSQL 的 bin log 沒有災難恢復能力,所以 InnoDB 使用 redo log 來實現 crash-safe 能力,確保任何事務提交後數據都不會丟失。

總結

關於 MySQL undo log、redo log、bin log 的總結就到這裏了,還是跟開頭說的一樣,最好結合之前我寫的《一條 Update 語句的執行過程是怎樣的?》一起看,因爲這幾個日誌就是在整個流程會用到的。

細心的朋友會發現 check point 的圓形圖很彆扭,是的,一開始其實我是不知道怎麼下手的,也沒找到有類似弧度的東西。

其實我可以從網上借個圖的,但是我用兩個大小圓,然後結合圖層和塗改器,然後把這個呈現出來了,雖然有點醜,但是自己配出來的,哈哈!你看這底圖的組裝和塗顏色還是做了不少東西的,【多試試,也許就成了】!

參考:

《Redis 設計與實現》

「MySQL 高級篇」MySQL 日誌、事務原理 -- undolog、redolog、binlog、兩階段提交

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Iyr9irGxu07oK0Aq82_GaQ