MySQL系列2:InnoDB存儲引擎

1. 架構回顧

上一篇我們講解了MySQL的邏輯架構,重新回顧一下,用一張新的圖來認識一下該架構。

整體架構分爲service層與存儲引擎層,請求交給連接池後,由後臺線程處理,並將請求轉發給SQL接口,隨後交給解析器執行,如果解析器發現命中緩存,直接從緩存讀數據返回,如果沒有,依次往下執行,直到從存儲引擎再到磁盤或者內存(存儲引擎對應的緩存中)查詢結果返回。

2. 三種日誌

在聊存儲引擎前,不得不聊三種日誌,undo log、redo log、binlog,因爲存儲引擎的執行過程中時刻跟寫日誌與刷盤有關係。

2.1 undo log

undo log是做回滾用的,記錄了某一次數據更新或者修改的逆向操作,比如現需要修改記錄,將a=1更新成a=2,undo log就記錄執行逆向操作,將a=2更新成a=1,再比如將某一條數據刪除,undo log就記錄改數據的恢復操作,insert該數據,保證數據操作不成功,通過undo log能恢復到修改前的版本。

2.2 redo log

redo log主要是保證數據修改不丟失。該日誌屬於存儲引擎層,屬於物理日誌,記錄修改了哪些數據。有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啓,之前提交的記錄都不會丟失,這個能力稱爲 crash-safe。

2.3 binlog

binlog屬於service層,記錄了sql功能上做的操作,屬於邏輯日誌,比如執行了什麼樣的sql更新語句等。主要用來做歷史數據恢復與主從同步。

2.4 binlog與redo log區別

  1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。
  2. redo log 是物理日誌,記錄的是“在某個數據頁上做了什麼修改”;binlog 是邏輯日誌,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
  3. redo log 是循環寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小後會切換到下一個,並不會覆蓋以前的日誌。

3. InnoDB的內存結構Buffer Pool以及執行流程

InnoDB存儲引擎中有一個非常重要的放在內存裏的組件,就是緩衝池(Buffer Pool),這裏面會緩存很多的數據,以便於以後在查詢的時候,萬一你要是內存緩衝池裏有數據,就可以不用去查磁盤了。我們先要明確一點事實,就是所有的操作都是基於Buffer Pool進行操作,而不是磁盤,因爲Buffer Pool內存操作速度快。舉個例子,引擎要更新“id=10”這一行數據 ,先看一下下圖的執行流程。

  1. 現將該數據從磁盤加載到緩存;
  2. 將數據的逆向操作記錄到undo log日誌,保存舊值,未來如果事務未提交可以執行回滾,恢復原始數據;
  3. 在Buffer Pool中更新數據;
  4. 將Redo Log寫入Redo Log Buffer;
  5. 執行Redo Log的第一個階段,也叫prepare準備階段,將Redo Log日誌刷到對應的磁盤文件;
  6. 執行binlog刷盤,將binlog日誌也刷到對應的磁盤文件;
  7. 執行Redo Log的第二個階段,也叫commit提交階段,對應的binlog文件名稱和這次更新的binlog日誌在文件裏的位置,都寫入到redo log日誌文件裏去,同時在redo log日誌文件裏寫入一個commit標記。在完成這個事情之後,纔算最終完成了事務的提交。

上面流程還涉及其他細節,比如刷盤策略,爲何要兩階段等,將在下面一一展開。

3.1 Redo Log刷盤策略

redo log有三種刷盤策略,該策略是通過innodb_flush_log_at_trx_commit來配置的,0-不刷磁盤,1-刷磁盤(建議),2-刷os cache,下圖分析如果刷os cache,默認1s以後才能刷到磁盤,期間宕機會導致數據丟失,如下圖。

如果設置不刷盤,Buffer Pool清空後數據也一樣丟失,所以建議設置參數爲1。如果上述刷盤不成功,第一階段事務就沒成功,後續binlog就根本不會執行,整個事務都會回滾,相當於更新白做。

3.2 binlog刷盤策略

sync_binlog參數可以控制binlog的刷盤策略,他的默認值是0,此時你把binlog寫入磁盤的時候,其實不是直接進入磁盤文件,而是進入os cache內存緩存。所以跟之前分析的一樣,如果此時機器宕機,那麼你在os cache裏的binlog日誌是會丟失的,我們看下圖的示意。

如果要是把sync_binlog參數設置爲1的話,那麼此時會強制在提交事務的時候,把binlog直接寫入到磁盤文件裏去,那麼這樣提交事務之後,哪怕機器宕機,磁盤上的binlog是不會丟失的。

3.2 Redo Log的兩階段提交

當我們把binlog寫入磁盤文件之後,接着就會完成最終的事務提交,此時會把本次更新對應的binlog文件名稱和這次更新的binlog日誌在文件裏的位置,都寫入到redo log日誌文件裏去,同時在redo log日誌文件裏寫入一個commit標記。在完成這個事情之後,纔算最終完成了事務的提交,我們看下圖的示意

最後一步在redo日誌中寫入commit標記的意義是什麼?說白了,他其實是用來保持redo log日誌與binlog日誌一致的。我們來舉個例子,假設我們在提交事務的時候,一共有上圖中的5、6、7三個步驟,必須是三個步驟都執行完畢,纔算是提交了事務。那麼在我們剛完成步驟5的時候,也就是redo log剛刷入磁盤文件的時候,mysql宕機了,此時怎麼辦?這個時候因爲沒有最終的事務commit標記在redo日誌裏,所以此次事務可以判定爲不成功。不會說redo日誌文件裏有這次更新的日誌,但是binlog日誌文件裏沒有這次更新的日誌,不會出現數據不一致的問題。

如果要是完成步驟6的時候,也就是binlog寫入磁盤了,此時mysql宕機了,怎麼辦?同理,因爲沒有redo log中的最終commit標記,因此此時事務提交也是失敗的。

必須是在redo log中寫入最終的事務commit標記了,然後此時事務提交成功,而且redo log裏有本次更新對應的日誌,binlog裏也有本次更新對應的日誌 ,redo log和binlog完全是一致的。

下面有圖來展示一下這個兩階段提交的過程

prepare 階段:將 XID(內部 XA 事務的 ID) 寫入到 redo log,同時將 redo log 對應的事務狀態設置爲 prepare,然後將 redo log 持久化到磁盤;
commit 階段:把 XID 寫入到 binlog,然後將 binlog 持久化到磁盤,接着調用引擎的提交事務接口,將 redo log 狀態設置爲 commit,此時該狀態並不需要持久化到磁盤,只需要 write 到文件系統的 page cache 中就夠了,因爲只要 binlog 寫磁盤成功,就算 redo log 的狀態還是 prepare 也沒有關係,一樣會被認爲事務已經執行成功。

通過這種兩階段提交的方案,就能夠確保redo-log、bin-log兩者的日誌數據是相同的。

3.3 後臺IO線程隨機將內存更新後的髒數據刷回磁盤

現在我們假設已經提交事務了,此時一次更新“update users set name='xxx' where id=1”,他已經把內存裏的buffer pool中的緩存數據更新了,同時磁盤裏有redo日誌和binlog日誌,都記錄了把我們指定的“id=1”這行數據修改了“name='xxx'”。此時我們會思考一個問題了,但是這個時候磁盤上的數據文件裏的“id=1”這行數據的name字段還是等於舊的值啊!所以MySQL有一個後臺的IO線程,會在之後某個時間裏,隨機的把內存buffer pool中的修改後的髒數據給刷回到磁盤上的數據文件裏去,我們看下圖:

當上圖中的線程把buffer pool裏的修改後的髒數據刷回磁盤的之後,磁盤上的數據纔會跟內存裏一樣,都是name=xxx這個修改以後的值了!在你線程把髒數據刷回磁盤之前,哪怕mysql宕機崩潰也沒關係,因爲重啓之後,會根據redo日誌恢復之前提交事務做過的修改到內存裏去,就是id=1的數據的name修改爲了xxx,然後等適當時機,線程自然還是會把這個修改後的數據刷到磁盤上的數據文件裏去的。

4 總結

大家通過一次更新數據的流程,就可以清晰地看到,InnoDB存儲引擎主要就是包含了一些buffer pool、redo log buffer等內存裏的緩存數據,同時還包含了一些undo日誌文件,redo日誌文件等東西,同時mysql server自己還有binlog日誌文件。在執行更新的時候,每條SQL語句,都會對應修改buffer pool裏的緩存數據、寫undo日誌、寫redo log buffer幾個步驟;但是當你提交事務的時候,一定會把redo log刷入磁盤,binlog刷入磁盤,完成redo log中的事務commit標記;最後後臺的IO線程會隨機的把buffer pool裏的髒數據刷入磁盤裏去。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章