1. 程式人生 > >MySQL · 原始碼分析 · Innodb 引擎Redo日誌儲存格式簡介

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));
linux技術交流群:295294329