MySQL InnoDB Recovery過程解析
來自專欄資料庫內核17 人贊了文章
本文由網易資料庫和大數據資深專家蔣鴻翔分享。原發表於知乎 網易雲社區。
InnoDB如果發生意外宕機了,數據會丟么?對於這個問題,稍微了解一點MySQL知識的人,都會斬釘截鐵的回答:不會!為什麼?他們也會毫不猶豫的說:因為有重做日誌(redo log),數據可以通過redo log進行恢復。回答得很好,那麼InnoDB怎樣通過redo log進行數據的恢復的,具體的流程是怎樣的?估計能說清楚這個問題的人剩的不多了,更深入一點:除了redo log,InnoDB在恢復過程中,還需要其他信息么?比如是否需要binlog參與?undo日誌在恢復過程中又會起到什麼作用?到這裡,可能很多人會變得疑惑起來:數據恢復跟undo有半毛錢的關係?
其實,InnoDB的數據恢復是一個很複雜的過程,在其恢復過程中,需要redo log、binlog、undo log等參與,這裡把InnoDB的恢復過程主要劃分為兩個階段,第一階段主要依賴於redo log的恢復,而第二階段,恰恰需要binlog和undo log的共同參與,接下來,我們來具體了解下整個恢復的過程。第一階段,資料庫啟動後,InnoDB會通過redo log找到最近一次checkpoint的位置,然後根據checkpoint相對應的LSN開始,獲取需要重做的日誌,接著解析獲取的日誌並且保存到一個哈希表中,最後通過遍歷哈希表中的redo log信息,讀取相關頁進行恢復。
InnoDB的checkpoint信息保存在日誌文件中,即ib_logfile0的開始2048個位元組中,checkpoint有兩個,交替更新,checkpoint與日誌文件的關係如下圖:checkpoint信息分別保存在ib_logfile0的512位元組和1536位元組處,每個checkpoint默認大小為512位元組,InnoDB的checkpoint主要有3部分信息組成:
- checkpoint no
checkpoint no主要保存的是checkpoint號,因為InnoDB有兩個checkpoint,通過checkpoint號來判斷哪個checkpoint更新
- checkpoint lsn
checkpoint lsn主要記錄了產生該checkpoint是flush的LSN,確保在該LSN前面的數據頁都已經落盤,不再需要通過redo log進行恢復
- checkpoint offset
checkpoint offset主要記錄了該checkpoint產生時,redo log在ib_logfile中的偏移量,通過該offset位置就可以找到需要恢復的redo log開始位置。
通過以上checkpoint的信息,我們可以簡單得到需要恢復的redo log的位置,然後通過順序掃描該redo log來讀取數據,比如我們通過checkpoint定位到開始恢復的redo log位置在ib_logfile1中的某個位置,那麼整個redo log掃描的過程可能是這樣的:- 從ib_logfile1的指定位置開始讀取redo log,每次讀取4 * page_size的大小,這裡我們默認頁面大小為16K,所以每次讀取64K的redo log到緩存中,redo log每條記錄(block)的大小為512位元組
- 讀取到緩存中的redo log通過解析、驗證等一系列過程後,把redo log的內容部分保存到用於恢復的緩存recv_sys->buf,保存到恢復緩存中的每條信息主要包含兩部分:(space,offset)組成的位置信息和具體redo log的內容,我們稱之為body
- 同時保存在恢復緩存中的redo信息會根據space,offset計算一個哈希值後保存到一個哈希表(recv_sys->addr_hash)中,相同的哈希值不同(space,offset)用鏈表存儲,相同的(space,offset)用列表保存,可能部分事務比較大,redo信息一個block不能保存,所以,每個body中可以用鏈錶鏈接多body的值
優化一
在根據(space, offset)讀取數據頁信息到buffer pool的時候,InnoDB不是只讀取一張頁面,而是讀取相鄰的32張頁面到buffer pool,這裡有個假設,InnoDB認為,如果一張頁面被修改了,那麼其周圍的一些頁面很有可能也被修改了,所以一次性連續讀入32張頁面可以避免後續再重新讀取。優化二在MySQL 5.7版本以前,InnoDB恢復的時候需要依賴數據字典,因為InnoDB根本不知道某個具體的space對應的ibd文件是哪個,這些信息都是數據字典維護的,而且在恢復前,需要把所有的表空間全部打開,如果庫中有數以萬計的表,把所有表打開一遍,整個過程就會很慢。那麼MySQL 5.7在這上面做了哪些改進呢?其實很簡單,針對上面的問題,InnoDB在redo log中增加了兩種redo log的類型來解決。MLOG_FILE_NAME用於記錄在checkpoint之後,所有被修改過的信息(space, filepath);MLOG_CHECKPOINT用於標誌MLOG_FILE_NAME的結束。
上面兩種redo log類型的添加,完美解決了前面遺留的問題,redo log中保存了後續需要恢復的space和filepath對,所以,在恢復的時候,只需要從checkpoint的位置往後掃描到MLOG_CHECKPOINT的位置,這樣就能獲取到需要恢復的space和filepath,在恢復過程中,只需要打開這些ibd文件即可,當然由於space和filepath的對應關係通過redo存了下來,恢復的時候也不再依賴數據字典。這裡需要強調的一點就是MLOG_CHECKPOINT在每個checkpoint點中最多只存在一次,如果出現多次MLOG_CHECKPOINT類型的日誌,則說明redo已經損壞,InnoDB會報錯。最多存在一次,那麼會不會有不存在的情況?答案是肯定的,在每次checkpoint過後,如果沒有發生數據更新,那麼MLOG_CHECKPOINT就不會被記錄。所以只要簡單查找下redo log最新一個checkpoint後的MLOG_CHECKPOINT是否存在,就能判定上次MySQL是否正常關機。5.7版本的MySQL在InnoDB進行恢復的時候,也正是這樣做的,MySQL 5.7在進行恢復的時候,一般情況下需要進行最多3次的redo log掃描:- 第一次redo log的掃描,主要是查找MLOG_CHECKPOINT,不進行redo log的解析,如果沒有找到MLOG_CHECKPOINT,則說明InnoDB不需要進行recovery,後面的兩次掃描可以省略,如果找到了MLOG_CHECKPOINT,則獲取MLOG_FILE_NAME到指定列表,後續只需打開該鏈表中的表空間即可。
- 第二次掃描是在第一次找到MLOG_CHECKPOINT基礎之上進行的,該次掃描會把redo log解析到哈希表中,如果掃描完整個文件,哈希表還沒有被填滿,則不需要第三次掃描,直接進行recovery就結束。
- 第三次掃描是在第二次基礎上進行的,第二次掃描把哈希表填滿後,還有redo log剩餘,則需要循環進行掃描,哈希表滿後立即進行recovery,直到所有的redo log被apply完為止。
redo log全部被解析並且apply完成,整個InnoDB recovery的第一階段也就結束了,在該階段中,所有已經被記錄到redo log但是沒有完成數據刷盤的記錄都被重新落盤。然而,InnoDB單靠redo log的恢復是不夠的,這樣還是有可能會丟失數據(或者說造成主從數據不一致),因為在事務提交過程中,寫binlog和寫redo log提交是兩個過程,寫binlog在前而redo提交在後,如果MySQL寫完binlog後,在redo提交之前發生了宕機,這樣就會出現問題:binlog中已經包含了該條記錄,而redo沒有持久化。binlog已經落盤就意味著slave上可以apply該條數據,redo沒有持久化則代表了master上該條數據並沒有落盤,也不能通過redo進行恢復。這樣就造成了主從數據的不一致,換句話說主上丟失了部分數據,那麼MySQL又是如何保證在這樣的情況下,數據還是一致的?這就需要進行第二階段恢復。
前面提到,在第二階段恢復中,需要用到binlog和undo log,下面我們就來看下具體的恢復邏輯是怎樣的?其實在該階段的恢復中,也被劃分成兩部分,第一部分,根據binlog獲取所有可能沒有提交事務的xid列表;第二部分,根據undo中的信息構造所有未提交事務鏈表,最後通過上面兩部分協調判斷事務是否可以提交。
如上圖中所示,MySQL在第二階段恢復的時候,先會去讀取最後一個binlog文件的所有event信息,然後把xid保存到一個列表中,然後進行第二部分的恢復,如下:
我們知道,InnoDB當前版本有128個回滾段,每個回滾段中保存了undo log的位置指針,通過掃描undo日誌,我們可以構造出還未被提交的事務鏈表(存在於insert_undo_list和update_undo_lsit中的事務都是未被提交的),所以通過起始頁(0,5)下的solt信息可以定位到回滾段,然後根據回滾段下的undo的slot定位到undo頁,把所有的undo信息構建一個undo_list,然後通過undo_list再創建未提交事務鏈表trx_sys->trx_list。
基於上面兩步, 我們已經構建了xid列表和未提交事務列表,那麼在這些未提交事務列表中的事務,哪些需要被提交?哪些又該回滾?判斷條件很簡單:凡是xid在通過binlog構建的xid列表中存在的事務,都需要被提交,換句話說,所有已經記錄binlog的事務,需要被提交,而剩下那些沒有記錄binlog的事務,則需要被回滾。通過上述兩個階段的數據恢復,InnoDB才最終完成整個recovery過程,回過頭來我們再想想,在上述兩個階段中,是否還有優化空間?比如第一階段,在構造完哈希表後,事務的恢復是否可以並發進行?理論上每個hash node是根據space,offset生成的,不同的hash node之間不存在衝突,可以並行進行恢復。或者在根據哈希表進行數據頁讀取時,每次讀取連續32張頁面,這裡讀取的32張頁面,可能有部分是不需要的,也同時被讀入到Buffer Pool中了,是否可以在構建一顆紅黑樹,根據space,offset組合鍵進行插入,這樣如果需要恢復的時候,可以根據紅黑樹的排序原理,把所有頁面的讀取順序化,並不需要讀取額外的頁面。以上純粹是一些個人想法,文章最後,整理一張恢復過程流程圖:本文來自網易雲社區,經作者蔣鴻翔授權發布。
原文地址:InnoDB recovery過程解析
更多網易研發、產品、運營經驗分享請訪問網易雲社區。
推薦閱讀:
※MySQL MGR成員管理與故障恢復實現
※MySQL與主流分支版本簡介
※來不及解釋了!看OceanBase SQL團隊如何優雅處理4200萬條/秒SQL峰值
※Mysql學習第三篇
※SQL習題解答總結