MySQL redo log恢復(fù)原理 | StoneDB技術(shù)分享會(huì) #5


StoneDB開(kāi)源地址
https://github.com/stoneatom/stonedb

設(shè)計(jì):小艾審核:丁奇、李浩責(zé)編:宇亭
作者:羅中天
浙江大學(xué)-軟件工程-在讀碩士、StoneDB 內(nèi)核研發(fā)實(shí)習(xí)生
2023 年 StoneDB 開(kāi)源之夏項(xiàng)目中選學(xué)生
redo log 類型
innodb 的 redo log 是帶有邏輯意義的物理日志:物理指的是 redo log 是針對(duì)某一個(gè)頁(yè)來(lái)說(shuō)的,每條 redo log 都會(huì)有 Type、Space ID、Page Number 等信息,如下圖所示;邏輯指的是一條 redo log 中可能描述的不是在頁(yè)面上的某個(gè)偏移量的位置上寫(xiě)入若干個(gè)字節(jié)的數(shù)據(jù),而是描述在頁(yè)面上插入或者刪除一條什么樣的記錄。
redo log 的通用結(jié)構(gòu)為
Type 的最高位是一個(gè) Single Record Flag 標(biāo)志位,如果為 1,表示該 redo log 單獨(dú)構(gòu)成一個(gè) mtr。
redo log 根據(jù)作用的對(duì)象,又可以分為作用于 Page 的 redo log,作用于 space 的 redo log 和提供額外信息的 redo log。
作用于 page 的 redo log
大多數(shù)的 redo log 屬于這一類別,常見(jiàn)的有 MLOG_1BYTE、MLOG_2BYTES、MLOG_4BYTES、MLOG_8BYTES、MLOG_REC_INSERT、MLOG_REC_CLUST_DELETE_MARK、MLOG_REC_UPDATE_IN_PLACE 等。其中 MLOG_1BYTE、MLOG_2BYTES、MLOG_4BYTES、MLOG_8BYTES 描述了在頁(yè)面的某個(gè)偏移量處寫(xiě)入若干個(gè)字節(jié)的數(shù)據(jù);MLOG_REC_INSERT 描述了在頁(yè)面上插入一條記錄;MLOG_REC_CLUST_DELETE_MARK 描述了在聚簇索引的頁(yè)面上刪除一條記錄(用戶線程刪除的操作只會(huì)打 delete 標(biāo)記,物理刪除的操作由 purge 線程來(lái)做);MLOG_REC_UPDATE_IN_PLACE 描述了在聚簇索引的頁(yè)面上原地更新一條記錄(即修改的是非索引列的字段,二級(jí)索引上的更新不會(huì)產(chǎn)生該條日志,因?yàn)槎?jí)索引上的記錄沒(méi)有版本鏈,所以更新操作產(chǎn)生的 redo log 為 MLOG_REC_CLUST_DELETE_MARK + MLOG_REC_INSERT)。
MLOG_REC_INSERT
MLOG_REC_INSERT 類型的 redo log body 部分的格式為
可見(jiàn),MLOG_REC_INSERT 類型的 redo log 進(jìn)行了前綴壓縮
MLOG_REC_CLUST_DELETE_MARK
MLOG_REC_UPDATE_IN_PLACE
作用于 space 的 redo log
這類 redo log 描述的是針對(duì)一個(gè) space 文件的修改,由于這類文件不是 write ahead 的,而是在文件操作后才記錄的,所以在恢復(fù)的過(guò)程中只會(huì)對(duì)于文件的狀態(tài)做一些檢查。這類 rede log 不是本文的重點(diǎn),在后續(xù)不再贅述。
提供額外信息的 redo log
這一類的 redo log 主要指的是 MLOG_MULTI_REC_END,只由一個(gè)字節(jié)的 Type 構(gòu)成,用于標(biāo)識(shí)一個(gè) mini transaction(簡(jiǎn)稱 mtr)的結(jié)尾。
recovery 原理
innodb 的 recovery 從 innodb 啟動(dòng)的時(shí)候開(kāi)始執(zhí)行,大概流程如下:
1、從 ib_logfile 文件的 header 中找到 checkpoint lsn,作為 recovery 的起點(diǎn)
2、每次從 ib_logfile 文件中讀取 64KB 的 redo log 到內(nèi)存中
3、將每個(gè) log block 的 header 和 trail 去掉后,拼出一份連續(xù)的日志
4、以 mtr 為單位進(jìn)行解析
4.1、判斷 MLOG_SINGLE_REC_FLAG 標(biāo)志位,如果一個(gè) mtr 只由單條日志構(gòu)成,直接解析后放入哈希表;
4.2、如果一個(gè) mtr 由多條日志構(gòu)成,需要先找到 MLOG_MULTI_REC_END 類型的日志,確定 mtr 的終點(diǎn),并加入緩存中,然后將緩存中所有的日志都放入哈希表中
5、將哈希表中的 redo log 進(jìn)行重放
note:這里不直接在解析的時(shí)候回放,而是插入哈希表中回放的好處是:可能會(huì)有很多 redo log 作用在同一個(gè) page 上,將這些 redo log 使用一次 IO 進(jìn)行重放,可以加快重放的速度。該哈希表包括兩層,第一層以 space_id 為 key,第二層以 page_no 為 key。
調(diào)用棧如下所示(下面的源碼基于 MySQL8.0.30 版本)
下面對(duì)從 recv_recovery_begin 開(kāi)始的流程進(jìn)行詳細(xì)闡述,在解析 redo log 的時(shí)候以解析 MLOG_REC_INSERT 類型的 redo log 為例進(jìn)行分析。為了突出主干,對(duì)代碼做了簡(jiǎn)化。
innodb 將解析和重放的邏輯是寫(xiě)在一起的,當(dāng)傳入的 block 為空時(shí),只解析不重放,當(dāng)傳入的 block 非空時(shí),解析并且重放。
recv_recovery_begin
該函數(shù)負(fù)責(zé)循環(huán)從 ib_logfile 文件中讀取 64KB 的 redo log 到內(nèi)存中進(jìn)行解析,并放入哈希表中
recv_read_log_seg
該函數(shù)負(fù)責(zé)從 ib_logfile 文件中讀取 64KB 的 redo log 到內(nèi)存中。
recv_scan_log_recs
該函數(shù)先將每個(gè) log block 的 header 和 trail 去掉后,拼出一份連續(xù)的日志,然后以 mtr 為單位進(jìn)行解析
recv_parse_log_recs
該函數(shù)判斷 MLOG_SINGLE_REC_FLAG 標(biāo)志位,根據(jù)一個(gè) mtr 是由一條日志組成還是多條日志組成,分開(kāi)處理。
recv_single_rec
單條 redo log 構(gòu)成的 mtr 的解析,將單條 redo log 解析后插入到哈希表中。
recv_multi_rec
多條 redo log 構(gòu)成的 mtr 的解析。
先確定 mtr 的重點(diǎn),并將解析好的 redo log 加入緩存中,遍歷該 mtr 中所有的 redo log,從緩存中取出后插入到哈希表中。
recv_parse_log_rec
該函數(shù)負(fù)責(zé)對(duì)單條 redo log 日志進(jìn)行解析,先解析 Type、Space ID、Page Number,再解析 body
mlog_parse_initial_log_record
該函數(shù)負(fù)責(zé)解析 Type、Space ID、Page Number
recv_parse_or_apply_log_rec_body
該函數(shù)負(fù)責(zé)解析 body,枚舉所有的 type 類型,分別進(jìn)行處理。
這里以 MLOG_REC_INSERT 的日志為例,會(huì)先解析字段數(shù)量、主鍵數(shù)量、字段長(zhǎng)度等信息,構(gòu)建出索引字典,然后解析剩余的部分,構(gòu)建出完整的記錄,最后插入對(duì)應(yīng)的頁(yè)中。
mlog_parse_index
該函數(shù)負(fù)責(zé)解析字段的數(shù)量,主鍵的數(shù)量和每個(gè)字段的長(zhǎng)度,構(gòu)建索引字典
parse_index_fields
該函數(shù)負(fù)責(zé)解析每個(gè)字段的長(zhǎng)度,填充索引的 field 列表
page_cur_parse_insert_rec
由于 MLOG_REC_INSERT 類型的 redo log 里做了壓縮,只記錄了和上一條記錄不一樣的部分,所以需要先解析出上一條記錄在頁(yè)面中的偏移量、待插入記錄和上一條記錄第一個(gè)不相同的字節(jié) mismatch_index 和待插入記錄從 mismatch_index 開(kāi)始的記錄長(zhǎng)度 eng_seg_len,然后定位到上一條記錄,取出前 mismatch_index 個(gè)字節(jié),并從 redo log 中解析出待插入記錄從 mismatch_index 開(kāi)始的部分,那么待插入記錄就是兩部分拼起來(lái),最后插入到 B+樹(shù)中。
總結(jié)
這篇文章我們介紹了 redo log 的分類,不同種類的 redo log 的結(jié)構(gòu),并且分析了 redo log 在恢復(fù)時(shí)的流程相關(guān)的源碼,歡迎大家關(guān)注StoneDB的開(kāi)源代碼。
StoneDB 介紹
StoneDB 是石原子科技自主設(shè)計(jì)研發(fā)的國(guó)內(nèi)首款完全兼容于 MySQL 生態(tài)的開(kāi)源 一體化實(shí)時(shí) HTAP 數(shù)據(jù)庫(kù)產(chǎn)品,具備行列混存、智能索引等核心特性,為 MySQL 數(shù)據(jù)庫(kù)提供在線數(shù)據(jù)實(shí)時(shí)就近分析服務(wù),能夠高效解決 MySQL 數(shù)據(jù)庫(kù)在分析型應(yīng)用場(chǎng)景中面臨的能力問(wèn)題。同時(shí),StoneDB 使用多存儲(chǔ)引擎架構(gòu)的設(shè)計(jì),事務(wù)引擎具有數(shù)據(jù)強(qiáng)一致特性,具備完整的事務(wù)并發(fā)處理能力,使得 StoneDB 可以替代 MySQL 數(shù)據(jù)庫(kù)滿足在線事務(wù)處理場(chǎng)景的需求,使用 MySQL 的用戶,通過(guò) StoneDB 可以實(shí)現(xiàn) TP+AP 混合負(fù)載,分析性能提升 10?倍以上顯著提升,不需要進(jìn)行數(shù)據(jù)遷移,也無(wú)需與其他 AP 集成,彌補(bǔ) MySQL 分析領(lǐng)域的空白。
加入StoneDB社區(qū)
Github:https://github.com/stoneatom/stonedb
Gitee:https://gitee.com/StoneDB/stonedb
社區(qū)官網(wǎng):https://stonedb.io/
嗶哩嗶哩:https://space.bilibili.com/1154290084
Twitter:https://twitter.com/StoneDataBase
Linkedin:https://www.linkedin.com/in/stonedb/
加入微信群:添加社區(qū)助理-小石俠;加入釘釘群:掃描下方釘釘群二維碼。
