閱讀109 返回首頁    go 人物


MySQL · 源碼分析 · Innodb 引擎Redo日誌存儲格式簡介

MySQL有多種日誌。不同種類、不同目的的日誌會記錄在不同的日誌文件中,它們可以幫助你找出mysqld內部發生的事情。比如錯誤日誌:用來記錄啟動、運行或停止mysqld進程時出現的問題;查詢日誌:記錄建立的客戶端連接和執行的語句;二進製日誌:記錄所有更改數據的語句,主要用於邏輯複製;慢日誌:記錄所有執行時間超過long_query_time秒的所有查詢或不使用索引的查詢。而對MySQL中最常用的事務引擎innodb,redo日誌是保證事務一致性非常重要的。本文結合MySQL版本5.6為分析源碼介紹MySQL innodb引擎的重做(Redo)日誌存儲格式。

Redo日誌

任何對Innodb表的變動, redo log都要記錄對數據的修改,redo日誌就是記錄要修改後的數據。redo 日誌是保證事務一致性非常重要的手段,同時也可以使在bufferpool修改的數據不需要在事務提交時立刻寫到磁盤上減少數據的IO從而提高整個係統的性能。這樣的技術推遲了bufferpool頁麵的刷新,從而提升了數據庫的吞吐,有效的降低了訪問時延。帶來的問題是額外的寫redo log操作的開銷。而為了保證數據的一致性,都要求WAL(Write Ahead Logging)。而redo 日誌也不是直接寫入文件,而是先寫入redo log buffer,而是批量寫入日誌。當需要將日誌刷新到磁盤時(如事務提交),將許多日誌一起寫入磁盤。關於redo的產生及其生命周期詳細過程,詳見:https://yq.aliyun.com/articles/219。

Redo日誌文件格式

MySQL redo日誌是一組日誌文件,它們會被循環使用。Redo log文件的大小和數目可以通過特定的參數設置,詳見innodb_log_file_size 和 innodb_log_files_in_group 。

日誌組結構

在實現上日誌組是由定義在log0log.h中的log_group_t結構體來表示的。在日誌組結構體定義中含有以下重要信息:
日誌文件的大小(file_size):記錄日誌組內每個日誌文件的大小,通過參數innodb_log_file_size配置。
日誌文件的個數(n_files): 記錄這個日誌組中的文件個數,,通過參數innodb_log_files_in_group配置。
Checkpoint相關的信息:隻有做完checkpoint後,其之前的日誌才可以不再保留,否則係統崩潰時則無法恢複。在係統崩潰後的恢複,需要從checkpoint點開始。但我們需要把checkpoint的相關信息持久化的保存下來,才能在係統崩潰時不會丟失這些檢查點相關的信息。Checkpoint相關的信息隻存放在ib _logfile0中。

日誌文件結構

每個日誌文件的前2048字節是存放的文件頭信息。頭結構定義在”storage/innobase/include/log0log.h” 中。其在重做日誌文件內的布局如下圖所示:

Redo 日誌存儲排列

其中幾個重要的字段在這裏加以說明:
日誌文件頭共占用4個OS_FILE_LOG_BLOCK_SIZE的大小,這裏對部分字段做簡要介紹:
1) LOG_GROUP_ID               這個log文件所屬的日誌組,占用4個字節,當前都是0;
2) LOG_FILE_START_LSN     這個log文件記錄的開始日誌的lsn,占用8個字節;
3) LOG_FILE_WAS_CRATED_BY_HOT_BACKUP   備份程序所占用的字節數,共占用32字節;
4) LOG_CHECKPOINT_1/LOG_CHECKPOINT_2   兩個記錄InnoDB checkpoint信息的字段,分別從文件頭的第二個和第四個block開始記錄,隻使用日誌文件組的第一個日誌文件。
從地址2KB偏移量開始,其後就是順序寫入的各個日誌塊(log block)。

日誌塊結構

所有的redo日誌記錄是以日誌塊為單位組織在一起的,日誌塊的大小為OS_FILE_LOG_BLOCK_SIZE(默認值為512字節),所有的日誌記錄以日誌塊為單位順序寫入日誌文件。每一條記錄都有自己的LSN(log sequence number, 表示從日誌記錄創建開始到特定的日誌記錄已經寫入的字節數)。每個日誌塊包含一個日誌頭段(12字節)、一個尾段(4字節),以及一組日誌記錄(512 – 12 – 4 = 496字節) 。

Redo 日誌塊結構

首先看下日誌塊頭結構。
1) log block number字段:占用日誌塊最開始的4個字節表示這是第幾個block塊。 其是通過LSN計算得來,計算的函數是log_block_convert_lsn_to_no();
2) block data len 字段:兩個字節表示該block中已經有多少個字節被使用; 若是整個塊都寫滿了日誌的話它的長度就應該是(OS_FILE_LOG_BLOCK_SIZE) 512 字節。
3) First Record offset 字段:占用兩個字節,表示該block中作為第一個新的mtr開始log record的偏移量。log_block_get_first_rec_group()就是用保存在這個字段的值,獲取到此塊中第一個新的mtr開始的日誌位置。
4) 中間496字節存放真正的Redo日誌。
5) Checksum字段:是塊的尾,占用四個字節,表示此log block計算出的校驗值,用於正確性校驗。

LSN和文件偏移量(offset)之間映射

在MySQL Innodb引擎中LSN是一個非常重要的概念,表示從日誌記錄創建開始到特定的日誌記錄已經寫入的字節數,LSN的計算是包含每個BLOCK的頭和尾字段的。那如何由一個給定LSN的日誌,在日誌文件中找到它存儲的位置的偏移量並能正確的讀出來呢。所有的日誌文件要屬於日誌組,而在log_group_t裏的lsn和lsn_offset字段已經記錄了某個日誌lsn和其存放在文件內的偏移量之間的對應關係。我們可以利用存儲在group內的lsn和給定lsn之間的相對位置,來計算出給定lsn在文件中的存儲位置。可以參考函數log_group_calc_lsn_offset()的實現。其核心代碼實現如下:

    gr_lsn = group->lsn;

    gr_lsn_size_offset = log_group_calc_size_offset(group->lsn_offset, group);

    group_size = log_group_get_capacity(group);

    if (lsn >= gr_lsn) {

        difference = lsn - gr_lsn;
    } else {
        difference = gr_lsn - lsn;

        difference = difference % group_size;

        difference = group_size - difference;
    }

    offset = (gr_lsn_size_offset + difference) % group_size;

    /* fprintf(stderr,
    "Offset is " LSN_PF " gr_lsn_offset is " LSN_PF
    " difference is " LSN_PF "\n",
    offset, gr_lsn_size_offset, difference);
    */

    return(log_group_calc_real_offset(offset, group));

最後更新:2017-09-21 09:03:46

  上一篇:go  MSSQL · 應用案例 · 日誌表設計優化與實現
  下一篇:go  MySQL · 特性分析 · 利用gdb跟蹤MDL加鎖過程