MySQL8.0新特性:增加系統檔案追蹤spaceID和物理檔案的對映

zhaiwx_yinfeng發表於2017-10-05

update:從8.0.11開始,又改成了開啟全部ibd檔案,但是改成了並行掃描

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),只有那些需要做崩潰恢復的檔案,才會被開啟。


相關文章