在上一篇《innodb源碼分析之重做日誌結構》中我們知道redo log的基本結構和日誌寫入步驟,那麼redo log是怎麼進行數據恢復的呢?在什麼時候進行redo log的日誌推演呢?redo log的推演只有在數據庫異常或者關閉後,數據庫重新啓動時會進行日誌推演,將數據庫狀態恢復到關閉前的狀態。那麼這個過程是怎麼進行的呢?以下我們逐步來解析。
1.recv_sys_t結構
struct recv_sys_struct
{
mutex_t mutex; /*保護鎖*/
ibool apply_log_recs; /*正在應用log record到page中*/
ibool apply_batch_on; /*批量應用log record標誌*/
dulint lsn;
ulint last_log_buf_size;
byte* last_block; /*恢復時最後的塊內存緩衝區*/
byte* last_block_buf_start; /*最後塊內存緩衝區的起始位置,因爲last_block是512地址對齊的,需要這個變量記錄free的地址位置*/
byte* buf; /*從日誌塊中讀取的重做日誌信息數據*/
ulint len; /*buf有效的日誌數據長度*/
dulint parse_start_lsn; /*開始parse的lsn*/
dulint scanned_lsn; /*已經掃描過的lsn序號*/
ulint scanned_checkpoint_no; /*恢復日誌的checkpoint 序號*/
ulint recovered_offset; /*恢復位置的偏移量*/
dulint recovered_lsn; /*恢復的lsn位置*/
dulint limit_lsn; /*日誌恢復最大的lsn,暫時在日誌重做的過程沒有使用*/
ibool found_corrupt_log; /*是否開啓日誌恢復診斷*/
log_group_t* archive_group;
mem_heap_t* heap; /*recv sys的內存分配堆,用來管理恢復過程的內存佔用*/
hash_table_t* addr_hash; /*recv_addr的hash表,以space id和page no爲KEY*/
ulint n_addrs; /*addr_hash中包含recv_addr的個數*/
};
在這個結構中,比較複雜的是addr_hash這個哈希表,這個哈希表是用sapce_id和page_no作爲hash key,裏面存儲有恢復時對應的記錄內容。恢復日誌在從日誌文件中讀出後,進行解析成若干個recv_t並存儲在哈希表當中。在一個讀取解析週期過後,日誌恢復會對hash表中的recv_t中的數據寫入到ibuf和page中。這裏爲什麼要使用hash表呢?個人覺得是爲了同一個page的數據批量進行恢復的緣故,這樣可以page減少隨機插入和修改。 以下是和這個過程相關的幾個數據結構:/*對應頁的數據恢復操作集合*/
struct recv_addr_struct
{
ulint state; /*狀態,RECV_NOT_PROCESSED、RECV_BEING_PROCESSED、RECV_PROCESSED*/
ulint space; /*space的ID*/
ulint page_no; /*頁序號*/
UT_LIST_BASE_NODE_T(recv_t) rec_list;
hash_node_t addr_hash;
};
/*當前的記錄操作*/
struct recv_struct
{
byte type; /*log類型*/
ulint len; /*當前記錄數據長度*/
recv_data_t* data; /*當前的記錄數據list*/
dulint start_lsn; /*mtr起始lsn*/
dulint end_lsn; /*mtr結尾lns*/
UT_LIST_NODE_T(recv_t) rec_list;
};
/*具體的數據體*/
struct recv_data_struct
{
recv_data_t* next; /*下一個recv_data_t,next的地址後面接了一大塊內存,用於存儲rec body*/
};
他們的內存關係結構圖如下:2.重做日誌推演過程的LSN關係
parse_start_lsn = scanned_lsn = recovered_lsn = 檢查點的lsn。
在日誌完成恢復時:
parse_start_lsn = 檢查點的lsn
scanned_lsn = recovered_lsn = log_sys->lsn。
3.日誌恢復的主要接口和流程
recv_recovery_from_checkpoint_finish 結束從重做日誌組內的checkpoint的數據恢復操作
recv_recovery_from_archive_start 從歸檔日誌文件中進行數據恢復
recv_recovery_from_archive_finish 結束從歸檔日誌中的數據恢復操作
recv_reset_logs 截取重做日誌最後一段作爲新的重做日誌的起始位置,可能會丟失數據。
2.然後調用recv_recovery_from_checkpoint_start,並將最大的LSN作爲參數傳入函數當中。
3.函數會先最近建立checkpoint的日誌組,並讀取出對應的checkpoint信息
4.通過checkpoint lsn和傳入的最大LSN進行比較,如果相等,不進行日誌恢復數據,如果不相等,進行日誌恢復。
5.在啓動恢復之前,先會同步各個日誌組的archive歸檔狀態
6.在開始恢復時,先會從日誌文件中讀取2M的日誌數據到log_sys->buf,然後對這2M的數據進行scan,校驗其合法性,而後將去掉block header的日誌放入recv_sys->buf當中,這個過程稱爲scan,會改變scanned lsn.
7.在對2M的日誌數據scan後,innodb會對日誌進行mtr操作解析,並執行相關的mtr函數。如果mtr合法,會將對應的記錄數據按space page_no作爲KEY存入recv_sys->addr_hash當中。
8.當對scan的日誌數據進行mtr解析後,innodb對會調用recv_apply_hashed_log_recs對整個recv_sys->addr_hash進行掃描,並按照日誌相對應的操作進行對應page的數據恢復。這個過程會改變recovered_lsn。
9.如果完成第8步後,會再次從日誌組文件中讀取2M數據,跳到步驟6繼續相對應的處理,直到日誌文件沒有需要恢復的日誌數據。
10.innodb在恢復完成日誌文件中的數據後,會調用recv_recovery_from_checkpoint_finish結束日誌恢復操作,主要是釋放一些開闢的內存。並進行事務和binlog的處理。