MySQL8.0新特性:增加系統檔案追蹤space ID和物理檔案的對映
Note1: 本文所有程式碼相關的內容都是基於MySQL8.0.3,而目前版本還處於RC和快速開發的狀態,不排除後面的版本邏輯,函式名等發生變化。
Note2: 主要程式碼在這個commit 中,感興趣的也可以自行閱讀程式碼
Note3: 本文僅是本人的閱碼筆記,記錄的比較凌亂。。。
前面我們提到了MySQL5.7的幾個崩潰恢復產生的效能退化 為了解決崩潰恢復的效率問題, MySQL8.0對crash recovery的邏輯進行了進一步的優化。 在之前的版本中,InnoDB通過向redo
log中寫入日誌來追蹤在一次checkpoint後修改過的表空間資訊,這樣就無需在crash recovery時開啟所有的表空間,只需蒐集哪些被影響到的表空間。而到了8.0新版本里,採用了一種全新的方式:單獨建立了系統對映檔案, 將space id及路徑資訊輪換著寫到兩個指定的系統檔案tablespaces.open.1 and tablespaces.open.2中(ref Fil_Open::write
實現的思路其實不復雜,就是將所有的表空間ID和對應的路徑資訊儲存到系統檔案中,在崩潰恢復時再按需開啟。
系統檔案更新
那麼如何保證所有的表空間資訊都一個不漏的儲存到系統檔案了呢 ? 實際上他跟蹤了所有的表空間檔案操作,並更新記憶體cache中(Fil_Open::m_spaces
),
如下:
a. fil_node_open_file
fil_system->m_open.enter();
fil_system->m_open.log(node->space->id, node->name);
fil_system->m_open.exit();
開啟表空間檔案後,寫一條日誌MLOG_FILE_OPEN, 並將表空間狀態 Nodes::OPEN以及日誌end lsn在記憶體中進行更新(Fil_Open::Nodes::load)
b. fil_node_close_file
fil_system->m_open.enter();
fil_system->m_open.close(node->space->id, node->name);
fil_system->m_open.exit();
關閉表空間檔案後, 將快取的表空間資訊LSN重置為0,並將狀態設定為CLOSED (Fil_Open::Nodes::close
c. fil_name_write_rename
fil_system->m_open.enter();
fil_system->m_open.log(space_id, new_name);
fil_system->m_open.to_file();
fil_system->m_open.exit();
在物理rename檔案之前, 將新的表空間名通過MLOG_FILE_OPEN寫到redo log中,記錄新檔案的狀態到記憶體。
隨後就將快取的表空間資訊寫到系統對映檔案中(Fil_Open::to_file
)
d. fil_delete_tablespace
fil_system->m_open.enter();
fil_system->m_open.deleted(id);
fil_system->m_open.exit();
在物理刪除檔案之後,將對應的表空間狀態設定為DELETED (Fil_Open::deleted
)
e. fil_ibd_create
fil_system->m_open.enter();
fil_system->m_open.open(space_id, file->name, log_get_lsn());
fil_system->m_open.exit();
在物理建立表空間檔案之後, 呼叫Fil_Open::open
將新檔案的資訊儲存到記憶體中。同樣的包含建立檔案時的LSN
可見InnoDB在對檔案進行開啟,關閉,建立,刪除,重新命名這些操作時都進行了追蹤,其中CREATE/DELETE/RENAME的cache更新均發生在記錄對應的MLOG_FILE_*日誌之前。
另外我們也可以看到,表空間資訊不是直接寫入的,而是經過zip壓縮後再寫的,以減少磁碟空間佔用。
那麼何時將快取的資訊刷到磁碟呢 ?
第一種情況是rename tablespace時,會做一次寫檔案
第二種情況是做checkpoint之前會去做一次flush(fil_tablespace_open_sync_to_disk
),
相比第一種情況,這裡先做一次清理(Fil_Open::purge -> Fil_Open::Nodes::purge
),將狀態為DELETED/MISSING的無效表空間記錄刪除掉,再刷到磁碟
當系統正常關閉時,InnoDB會去將系統檔案中的資訊全部清除掉(fil_tablespace_open_clear
),因為崩潰恢復無需用到。
崩潰恢復
那麼崩潰恢復時,如何使用該檔案呢?
首先在啟動時(srv_start
), 當確定了需要崩潰恢復時(recv_recovery_from_checkpoint_start
),就會去從系統對映檔案中載入表空間資訊到記憶體中(fil_tablespace_open_init_for_recovery
--> Fil_Open::from_file
)。
隨後開始讀redo log並解析, 如下堆疊:
recv_recovery_begin
|--> recv_scan_log_recs
|--> recv_parse_log_recs
|--> recv_single_rec
|--> recv_multi_rec
在將redo log加入到hash table之前,會先進行判斷,只有在檔案中找到的表空間,才需要去apply日誌。
if (space_id == TRX_SYS_SPACE
|| fil_tablespace_lookup_for_recovery(space_id)) {
recv_add_to_hash_table(
type, space_id, page_no, body,
ptr + len, old_lsn, recv_sys->recovered_lsn);
} else {
recv_sys->missing_ids.insert(space_id);
}
由於系統檔案不是實時flush的,因此在解析到MLOG_FILE_*型別的redo時, 也要對快取的表空間資訊進行修正(fil_tablespace_name_recover
--> fil_name_process_for_recovery
) ,以確保所有需要apply redo的tablespace都load到記憶體中。
在執行崩潰恢復時,InnoDB會按需去開啟表空間檔案,然後再去apply日誌。(recv_apply_hashed_log_recs
--> fil_tablespace_open_for_recovery
),只有那些需要做崩潰恢復的檔案,才會被開啟。