MySQL InnoDB Update和Crash Recovery流程
- 概要信息
- 首先介紹了Redo,Undo,Log Sequence Number (LSN),Checkpoint,Rollback Pointer (ROLL_PTR),Transaction ID (TRX_ID),Transaction Serialization Number(TRX_NO) 是什么?
- 然后介紹了MySQL Update過程中發生了什么?Redo,Undo,雙寫之間如何配合,臟頁何時刷新?
- 最后介紹了Crash Recovery時如何做恢復?
1、InnoDB 術語和概念
- 我們首先來InnoDB的一些基本屬於和概念,以便更好地理解下文中介紹的Update和Crash Recovery流程
1.1. InnoDB概述圖
1.2. InnoDB 重要術語和概念
-
1.2.1. 什么是Redo?
-
通常也會叫做"InnoDB log(s)",預先分配至少2個日志文件,第一個文件開頭和最后一個文件結尾進行首尾相連以循環的方式重復使用。"Redo"的意思是在必要時(如:崩潰恢復時)可以使用Redo Log中的數據來重新應用到InnoDB數據文件中,使得InnoDB能夠恢復到一個一致性狀態
-
Redo Log 是一個預寫日志(WAL),是一種用於在數據庫或數據庫所在主機發生崩潰時確保數據完整性的技術。Redo Log日志記錄必須在數據實際更改(buffer pool中的臟頁刷新到數據文件)之前寫入磁盤。因此,對數據表做修改時,每個數據記錄的修改都會寫入Redo Log Buffer中(作為重做日志記錄)。當一個頁面的修改操作完成時,Redo Log Buffer將執行flush到Redo Log文件操作(但此時可能並未sync到磁盤)
-
根據WAL日志先行原則,buffer pool中的臟頁被刷新到數據文件中之前,需要確保對應LSN號的Redo Log先sync到磁盤文件中,Redo Log的刷盤機制以及臟頁的刷盤機制確保了Redo Log總是先於臟頁落盤。另外,如果系統參數innodb_flush_log_at_trx_commit設置為1,則每個事務提交時也會將Redo Log sync到磁盤文件中
-
Redo Log 日志組結構
-
1.2.2. 什么是Undo?
-
用於撤消(或還原)對InnoDB中存儲的數據的變更及回滾事務,也用於實現多版本控制(mvcc),通過構建一致性視圖(read view)實現對數據庫的一致性讀
-
對數據庫的每一次更改,Undo Log都會保存之前版本的數據,每個聚簇(PK)索引記錄都有一個指向該修改記錄之前版本數據的指針(稱為“回滾指針”),每個Undo Log記錄都會存儲一個回滾指針指向之前版本的數據,另外,每個Undo Log的變更也必須記錄到Redo Log中
-
PS:Undo Log在共享表空間的基本結構
* 在共享表空間的第6個頁存放了InnoDB的事務系統信息,其中也包含了Undo Log的一些系統信息
* InnoDB事務系統信息頁結構
* InnoDB事務系統最多可以創建128個回滾段(MySQL 8.x版本除外),每個回滾段中都需要有一個單獨的page來維護其擁有的undo solt(通常是每個回滾段中的第一個頁),每個回滾段有1024個事務槽,每個事務槽指針都指向每個回滾段中的第一個UNDO_lOG頁中的回滾段頭
* Undo Log的數據存儲在系統表空間的UNDO_LOG頁中,下面分別是UNDO_LOG頁結構、UNDO_LOG頁中UNDO頁頭部和UNDO段頭部、UNDO_LOG記錄格式示意圖
-
1.2.3. 什么是Log Sequence Number (LSN)?
-
一個64位無符號整數,表示Redo Log系統中的時間點,也是事務寫入Redo Log的字節總量,從日志初始化開始計數(數據庫初始化安裝時間點開始且單調遞增)
-
LSN不僅存在於Redo Log中,在每個數據頁中都保存着一個LSN,在進行數據恢復時通過LSN做比較運算可以判斷出每個數據頁是否需要進行恢復操作
-
1.2.4. 什么是Checkpoint?
-
一個時間點,由一個LSN值(Checkpoint LSN)表示的整型值,在Checkpoint LSN之前的每個數據頁(buffer pool中的臟頁)的更改都已經落盤(刷新到數據文件中),Checkpoint 完成后,在Checkpoint LSN之前的Redo Log就不再需要了
-
Checkpoint技術是為了解決:全量Redo Log恢復時間太長、buffer pool中的空閑頁不夠用時將臟頁刷新到磁盤數據文件、Redo Log空間不夠用時將臟頁刷新到磁盤數據文件等問題
-
Checkpoint方式有兩種:Sharp Checkpoint和Fuzzy Checkpoint(又可根據不同的場景細分)
* Sharp Checkpoint:將所有的臟頁刷回磁盤,數據庫實例關閉時系統參數innodb_fast_shutdown設置為0,才需要把所有的臟頁都刷回磁盤,刷臟時系統hang住
* Fuzzy Checkpoint:持續的每次只刷新一部分臟頁到磁盤,數據庫正常運行過程中都是使用這種方式刷臟,在InnoDB內部還可細分為如下幾種:
** Master線程每秒/每十秒固定執行Checkpoint
** LRU list中空閑頁不夠時,觸發Checkpoint從LRU list刷新臟頁以釋放足夠的空閑頁
** Redo Log空間不夠時,觸發Checkpoint從Flush list刷新臟頁,Checkpoint執行完成之后,在這個位置之前的Redo Log不再需要(即,可以循環覆蓋使用)
** 臟頁太多達到臟頁比例閥值(系統參數innodb_max_dirty_pages_pct和innodb_max_dirty_pages_pct_lwm控制臟頁比例閥值),觸發Checkpoint -
1.2.5. 什么是Rollback Pointer (ROLL_PTR)?
-
一個由rollback segment number、page number和page offset組成的指針,指向Undo Log中包含之前版本數據的具體Undo Log日志記錄
-
可用於為任何數據記錄回退到一個歷史版本記錄、可用於mvcc中重建舊版本記錄、可用於事務回滾
-
1.2.6. 什么是Transaction ID (TRX_ID)?
-
表示事務開始點的一個64位無符號整數
-
每個事務的事務號增量增加
-
事務號會寫入聚簇索引的每個記錄中
-
最大事務號會寫入系統表空間的TRX_SYS頁
-
1.2.7. 什么是Transaction Serialization Number(TRX_NO) ?
-
一個64位無符號整數,表示事務提交時的最大TRX_ID
-
TRX_NO在事務提交時會寫入Undo Log Header
-
TRX_NO可用於purge Undo Log中的舊版本記錄
2、Update流程
-
2.1. 事務start(事務首次開啟):
-
為這個事務分配事務ID(TRX_ID),該事務ID可能被寫入系統表空間的TRX_SYS頁面中的最大的事務ID字段(Transaction ID)
* 如果系統表空間的TRX_SYS頁面中的最大的事務ID字段被更新,則該更新會被記錄到Redo Log中 -
根據分配的TRX_ID創建read view
-
2.2. 記錄修改(每次只修改一行記錄)
-
分配Undo Log日志空間
-
拷貝該記錄修改之前的值到Undo Log中
-
將Undo Log的修改記錄寫入Redo Log中
-
在buffer pool中修改數據頁,回滾段指針指向Undo Log中該記錄之前的版本
-
將該記錄對應的數據頁變更部分寫入Undo Log中
-
buffer pool中該記錄修改之后的數據頁被標記為"臟頁"(需要刷新到磁盤的數據頁)
-
2.3. 此時其他事務的修改會怎樣?
-
一旦記錄被修改,即使沒有提交,其他事務也可能會看到被修改后的記錄,這依賴於他們的事務隔離級別而定
* 如果是RU隔離級別,則使用索引頁讀取最新版本記錄
* 如果是RC隔離級別,則查找記錄的最新提交版本
* 如果是RR隔離級別,則查找與其read view相對應的記錄版本 -
任何需要使用索引頁來讀取比最新的版本記錄舊的版本記錄時,都必須使用Undo Log來重建之前的版本記錄
-
2.4. 事務提交(顯式和隱式提交)
-
事務對應的Undo Log頁被設置為"purge"(意味着當這個Undo Log頁不再被任何其他事務引用時可以將其清除)
-
將Undo Log的修改記錄寫入Redo Log中
-
Redo Log Buffer刷新到磁盤(是否刷盤取決於系統變量innodb_flush_log_at_trx_commit的設置)
-
2.5. 后台線程刷臟(后台線程連續不斷地根據不同觸發機制觸發刷新)
-
查找最舊的“臟”頁面(修改時間最早的頁面)並將其添加到flush batch中
-
確保在flush batch中中最新的LSN號已經寫入到了Redo Log中且已經落盤
-
如果開啟了雙寫,則先將臟頁刷新到雙寫緩沖區(並等待同步)
-
將每個臟頁從buffer pool中寫入最終目的地:表空間文件中的
-
PS:對於后台線程刷臟部分,執行刷新臟頁時,與該臟頁的事務是否提交無關,只需要確保該頁對應LSN號的Redo Log記錄落盤,而不會去判斷事務的狀態是否是提交還是未提交狀態,因為,數據頁結構中並沒有地方單獨記錄事務的狀態(即,無法判斷事務是否提交),只是在每行數據中有記錄事務號、回滾段指針(所以一個頁中也可能包含多個事務的修改記錄)。當需要對某個事務進行回滾時,重新從表空間中讀取這個未提交的臟頁,使用undo log中的反向數據進行反向修改,然后再重新刷臟。
-
2.6. 定期執行Checkpoint
-
確保比Checkpoint 點更舊(比Checkpoint LSN小)的臟頁已刷新到表空間文件,如果存在有比Checkpoint LSN大的臟頁,則立即刷新臟頁到數據文件中。說白了Checkpoint機制主要作用就是用於刷新臟頁
-
把Checkpoint LSN寫到Redo Log Header中 (從這個Checkpoint LSN開始,之前的Redo Log記錄不再需要)
-
2.7. 后台線程Purge(后台線程連續不斷地根據需要定期執行Purge,包括Undo Log和歷史鏈表)
-
查找每個回滾段中不再需要的最舊的Undo Log
-
實際上是從索引中刪除任何帶有刪除標記的記錄
-
釋放Undo Log頁
-
修剪history lists
3、Creash Recovery流程
-
3.1. 什么時候會進行Crash Recovery?
-
實例崩潰之后重啟
-
使用一個備份還原(如:LVM 快照、xtrabackup備份)后
-
在“快速”(innodb_fast_shutdown不為0值關閉實例)關閉實例后重新啟動
-
3.2. 檢測實例是不是干凈地關閉的
-
打開Redo Logs和系統表空間文件(ibdataN)
-
讀取並從中找到最大的Checkpoint LSN
-
從最近的Checkpoint 開始往后掃描Redo Log
-
如果能夠找到Redo Log記錄,說明還有數據頁的更改沒有刷新到數據文件上,啟動Crash Recovery,使用Redo Log來恢復數據的一致性
-
3.3. 使用所有獨立表空間的表名和表空間ID創建一個名稱到ID的映射
-
打開datadir下的所有.ibd文件
-
在這些表空間文件的offset 0的頁(FSP_HDR頁)頭讀取其表空間ID(FSP_HDR頁中FSP Header的前四個字節記錄着表空間ID)
-
將表空間ID與表名建立映射
-
PS:
* page offset 0(FSP_HDR頁)頁結構
* FSP_HDR頁中FSP Header的前四個字節記錄着表空間ID
-
3.4. 損壞頁修復(檢查是否有不完整的頁,如果有則使用Double Write Buffer進行修復)
-
檢查雙寫緩沖區中的所有128個頁:
* 讀取表空間中的每個“目標”頁
* 如果頁頭和頁尾的LSN不匹配或頁面校驗和無效,則使用雙寫緩沖區中的頁進行還原
* 如果該頁在雙寫緩沖區中的版本也被破壞,則server將crash -
3.5. 前滾Redo,回滾未提交事務
-
事務系統初始化(回滾段初始化)
-
從最近的Checkpoint 往后掃描到的Redo Log記錄將被應用到各個數據文件中
-
從Undo Log中恢復處於'ACTIVE'狀態的事務,重新生成read view
-
使用Undo Log回滾未提交的'ACTIVE'狀態的事務
-
處於PREPARE狀態的事務,如果打開了binlog且在binlog有找到對應事務的日志則重新提交,否則回滾
4、參考資料
- 本文大部分為譯文,原文PDF下載鏈接:
- https://www.percona.com/live/mysql-conference-2013/sites/default/files/slides/InnoDB - A journey to the core - PLMCE 2013.pdf
- https://www.percona.com/live/mysql-conference-2014/sites/default/files/slides/InnoDB - A journey to the core II.pdf
- https://www.percona.com/live/mysql-conference-2015/sites/default/files/slides/InnoDB - A journey to the core III.pdf
- 解析表空間工具:https://blog.jcole.us/2013/01/03/a-quick-introduction-to-innodb-ruby/
- 深入了解InnoDB學習資料:https://blog.jcole.us/innodb/
- MySQL · 引擎特性 · InnoDB 文件系統之文件物理結構:https://yq.aliyun.com/articles/51133
- 關於2.5. 小節的PS部分,參考了oracle的資料,個人覺得MySQL也是同樣的邏輯:http://blog.csdn.net/dba_waterbin/article/details/7823519
- PS:本博客系個人博客,所發布大部分內容會延后於微信公眾號"沃趣科技"和"DBGeeK" 以及其他任何與沃趣科技有合作的網站、公眾號 發布