天天看點

簡單談談MySQL的兩階段送出

一、簡單回顧三種日志

在講解兩階段送出之前,需要對MySQL中的三種日志即binlog、redo log與undo log有一定的了解。

在這三種日志中,很多同學會把binlog與redo log混淆,下面使用一張表格來簡單對比下兩者的差別。

簡單談談MySQL的兩階段送出

當然,如果對binlog與redo log有更深的興趣,可以參考我的另外一篇文章​​資料庫日志——binlog、redo log、undo log掃盲​​

這邊隻要記住兩者的歸屬于适用場景即可,binlog适用于維護叢集内資料的一緻性,而redo log用于崩潰恢複。

undo log相對于前面兩種日志更好了解些,就是為了復原事務用的。

MVCC原理其實就是undo log版本鍊與ReadView的結合,想要弄清楚如何在快照讀的情況下實作事務之間的隔離性,可以移步我的這篇文章​​通俗易懂的MySQL事務及MVCC原理,我先收藏了!​​

二、一條語句的執行過程

先從整體的角度來觀測某件事物,可以避免一上來就陷入局部的泥潭當中。

MySQL分為Server層與存儲引擎層,Server層包括連接配接器、分析器、優化器于執行器等。

而存儲引擎層被設計為支援可插拔式,可以支援InnoDB、MyISAM等存儲引擎。

一般來說,一條語句,不論是查詢還更新,都會走以下的流程。

簡單談談MySQL的兩階段送出

這部分不是本文的重點,就簡單說下各個元件的作用,大家有個印象就行。

連接配接器

用于和用戶端建立連接配接,管理連接配接。檢查連接配接中的使用者名密碼是否正确嗎,以及是否對表有操作權限。

分析器

隻要是進行詞法、文法分析,區分sql關鍵詞與非關鍵詞,生成一顆文法樹。如果生成文法樹失敗,則證明你的sql有文法錯誤。

之後對文法樹進行一些剪枝操作,去除一些無用的條件等。

優化器

生成sql的執行計劃,你可以使用explain來檢視執行計劃。

會基于某些規則來選擇走的索引項,在取樣的時候可能會存在誤差,可是使用force index來強制走某條索引。

執行器

依據執行計劃,調用存儲引擎的接口,來實作對資料的讀寫操作。

我們會在下一小節中,簡單講述執行器和存儲引擎在兩階段送出中的互動流程。

三、兩階段送出

先看執行器與InnoDB引擎是如何更新一條指定的資料的:

簡單談談MySQL的兩階段送出

可以看到,InnoDB在寫redo log時,并不是一次性寫完的,而有兩個階段,Prepare與Commit階段,這就是"兩階段送出"的含義。

為什麼要寫redo log,不寫redo log的話,根本就不會出現“兩階段送出”的麻煩事啊?

先說結論:在于崩潰恢複。

MySQL為了提升性能,引入了BufferPool緩沖池。查詢資料時,先從BufferPool中查詢,查詢不到則從磁盤加載在BufferPool。

每次對資料的更新,也不總是實時重新整理到磁盤,而是先同步到BufferPool中,涉及到的資料頁就會變成髒頁。

同時會啟動背景線程,異步地将髒頁重新整理到磁盤中,來完成BufferPool與磁盤的資料同步。

如果在某個時間,MySQL突然崩潰,則記憶體中的BufferPool就會丢失,剩餘未同步的資料就會直接消失。

雖然在更新BufferPool後,也寫入了binlog中,但binlog并不具備crash-safe的能力。

因為崩潰可能發生在寫binlog後,刷髒前。在主從同步的情況下,從節點會拿到多出來的一條binlog。

是以server層的binlog是不支援崩潰恢複的,隻是支援誤删資料恢複。InnoDB考慮到這一點,自己實作了redo log。

為什麼要寫兩次redo log,寫一次不行嗎?

先不談到底寫幾次redo log合适,如果隻寫一次redo log會有什麼樣的問題呢?

redo log與binlog都寫一次的話,也就是存在以下兩種情況:

先寫binlog,再寫redo log

目前事務送出後,寫入binlog成功,之後主節點崩潰。在主節點重新開機後,由于沒有寫入redo log,是以不會恢複該條資料。

而從節點依據binlog在本地回放後,會相對于主節點多出來一條資料,進而産生主從不一緻。

先寫redo log,再寫binlog

目前事務送出後,寫入redo log成功,之後主節點崩潰。在主節點重新開機後,主節點利用redo log進行恢複,就會相對于從節點多出來一條資料,造成主從資料不一緻。

是以,隻寫一次redo log與binlog,無法保證這兩種日志在事務送出後的一緻性。

也就是無法保證主節點崩潰恢複與從節點本地回放資料的一緻性。

在兩階段送出的情況下,是怎麼實作崩潰恢複的呢?

首先比較重要的一點是,在寫入redo log時,會順便記錄XID,即目前事務id。在寫入binlog時,也會寫入XID。

如果在寫入redo log之前崩潰,那麼此時redo log與binlog中都沒有,是一緻的情況,崩潰也無所謂。

如果在寫入redo log prepare階段後立馬崩潰,之後會在崩恢複時,由于redo log沒有被标記為commit。于是拿着redo log中的XID去binlog中查找,此時肯定是找不到的,那麼執行復原操作。