在之前的文章「簡單瞭解InnoDB底層原理」聊了一下MySQL的Buffer Pool。這裡再簡單提一嘴,Buffer Pool是MySQL記憶體結構中十分核心的一個組成,你可以先把它想象成一個黑盒子。
黑盒下的更新資料流程
當我們查詢資料的時候,會先去Buffer Pool中查詢。如果Buffer Pool中不存在,儲存引擎會先將資料從磁碟載入到Buffer Pool中,然後將資料返回給客戶端;同理,當我們更新某個資料的時候,如果這個資料不存在於Buffer Pool,同樣會先資料載入進來,然後修改修改記憶體的資料。被修改過的資料會在之後統一刷入磁碟。
這個過程看似沒啥問題,實則不講武德。假設我們修改Buffer Pool中的資料成功,但是還沒來得及將資料刷入磁碟MySQL就掛了怎麼辦?按照上圖的邏輯,此時更新之後的資料只存在於Buffer Pool中,如果此時MySQL當機了,這部分資料將會永久的丟失;
再者,我更新到一半突然發生錯誤了,想要回滾到更新之前的版本,該怎麼辦?那不完犢子嗎,連資料持久化的保證、事務回滾都做不到還談什麼崩潰恢復?
Redo Log & Undo Log
而通過MySQL能夠實現崩潰恢復的事實來看,MySQL必定實現了某些騷操作。沒錯,這就是接下來我們要介紹的另外的兩個關鍵功能,Redo Log和Undo Log。
這兩種日誌是屬於InnoDB儲存引擎的日誌,和MySQL Server的Binlog不是一個維度的日誌。
Redo Log 記錄了此次事務**「完成後」** 的資料狀態,記錄的是更新之 **「後」**的值 Undo Log 記錄了此次事務**「開始前」** 的資料狀態,記錄的是更新之 **「前」**的值
所以這兩種日誌有明顯的區別,我用一種更加通俗的例子來解釋一下這兩種日誌。
Redo Log就像你在命令列敲了很長的命令,敲回車執行,結果報錯了。此時我們只需要再敲個↑就會拿到上一條命令,再執行一遍即可。
Undo Log就像你剛剛在Git中Commit了一下,然後再做一個較為複雜的改動,但是改著改著你的心態崩了,不想要剛剛的改動了,於是直接git reset --hard $lastCommitId
回到了上一個版本。
實現日誌後的更新流程
有了Redo Log和Undo Log,我們再將上面的那張圖給完善一下。
首先,更新資料還是會判斷資料是否存在於Buffer Pool中,不存在則載入。上面我們提到了回滾的問題,在更新Buffer Pool中的資料之前,我們需要先將該資料事務開始之前的狀態寫入Undo Log中。假設更新到一半出錯了,我們就可以通過Undo Log來回滾到事務開始前。
然後執行器會更新Buffer Pool中的資料,成功更新後會將資料最新狀態寫入Redo Log Buffer中。因為一個事務中可能涉及到多次讀寫操作,寫入Buffer中分組寫入,比起一條條的寫入磁碟檔案,效率會高很多。
那為什麼Undo Log不也搞一個Undo Log Buffer,也給Undo Log提提速,雨露均沾?那我們假設有這個一個Buffer存在於InnoDB,將事務開始前的資料狀態寫入了Undo Log Buffer中,然後開始更新資料。
突然啪一下,很快啊,MySQL由於意外程式退出了,此時會發生一件很尷尬的事情,如果更新的資料一部分已經刷回磁碟了,但是此時事務沒有成功需要回滾,你發現Undo Log隨著程式退出一起沒了,此時就沒有辦法通過Undo Log去做回滾。
那如果剛剛更新完記憶體,MySQL就掛了呢?此時Redo Log Buffer甚至都可能沒有寫入,即使寫入了也沒有刷到磁碟,Redo Log也丟了。
其實無所謂,因為意外當機,該事務沒有成功,既然事務事務沒有成功那就需要回滾,而MySQL重啟後會讀取磁碟上的Redo Log檔案,將其狀態給載入到Buffer Pool中。而通過磁碟Redo Log檔案恢復的狀態和當機前事務開始前的狀態是一樣的,所以是沒有影響的。然後等待事務commit了之後就會將Redo Log和Binlog刷到磁碟。
流程中仍然存在的問題
你可能認為到這一步就完美了,事實上則不然。假設我們在將Redo Log刷入到磁碟之後MySQL突然當機了,binlog還沒有來得及寫入。此時重啟,Redo Log所代表的狀態就和Binlog所代表的狀態不一致了。Redo Log恢復到Buffer Pool中的某行的A欄位是3,但是任何監聽其Binlog的資料庫讀取出來的資料確是2。
即使Redo Log和Binlog都寫入檔案了,但是這個時候MySQL所在的物理機或者VM當機了,日誌仍然會丟失。現在的OS在你寫入檔案的時候,會先將改動的內容寫入的OS Cache中,以此來提高效率。然後根據策略(受你配置的引數的影響)來將OS Cache中的資料刷入磁碟。
基於2PC的一致性保障
從這你可以發現一個關鍵的問題,那就是必須保證Redo Log和Binlog在事務提交時的資料一致性,要麼都存在,要麼都不存在。MySQL是通過 **2PC(two-phase commit protocol)**來實現的。
簡單介紹一下2PC,它是一種保證分散式事務資料一致性的協議,它中文名叫兩階段提交,它將分散式事務的提交拆分成了2個階段,分別是Prepare和Commit/Rollback。
就向兩個拳擊手開始比賽之前,裁判會在中間確認兩個選手的狀態,類似於問你準備好了嗎?得到確認之後,裁判才會說Fight。
裁判詢問選手的狀態,對應的是第一階段Prepare;得到了肯定的回答之後,裁判宣佈比賽正式開始,對應的是第二階段Commit,但是如果有一方選手沒有準備好,裁判會宣佈比賽暫停,此時對應的是第一階段失敗的情況,第二階段的狀態會變為Rollback。裁判就對應2PC中的協調者Coordinator,選手就對應參與者Participant。
下面我們通過一張圖來看一下整個流程。
Prepare階段,將Redo Log寫入檔案,並刷入磁碟,記錄上內部XA事務的ID,同時將Redo Log狀態設定為Prepare。Redo Log寫入成功後,再將Binlog同樣刷入磁碟,記錄XA事務ID。
Commit階段,向磁碟中的Redo Log寫入Commit標識,表示事務提交。然後執行器呼叫儲存引擎的介面提交事務。這就是整個過程。
驗證2PC機制的可用性
這就是2PC提交Redo Log和Binlog的過程,那在這個期間發生了異常,2PC這套機制真的能保證資料一致性嗎?
假設Redo Log刷入成功了,但是還沒來得及刷入Binlog MySQL就掛了。此時重啟之後會發現Redo Log並沒有Commit標識,此時根據記錄的XA事務找到這個事務,進行回滾。
如果Redo Log刷入成功,而且Binlog也刷入成功了,但是還沒有來得及將Redo Log從Prepare改成Commit MySQL就掛了,此時重啟會發現雖然Redo Log沒有Commit標識,但是通過XID查詢到的Binlog卻已經成功刷入磁碟了。
此時,雖然Redo Log沒有Commit標識,MySQL也要提交這個事務。因為Binlog一旦寫入,就可能會被從庫或者任何消費Binlog的消費者給消費。如果此時MySQL不提交事務,則可能造成資料不一致。而且目前Redo Log和Binlog從資料層面上,其實已經Ready了,只是差個標誌位。
好了以上就是本篇部落格的全部內容了,如果你覺得這篇文章對你有幫助,還麻煩點個贊,關個注,分個享,留個言。
歡迎微信搜尋關注【SH的全棧筆記】,檢視更多相關文章