亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解InnoDB引擎

發布時間:2021-10-11 10:20:27 來源:億速云 閱讀:141 作者:柒染 欄目:MySQL數據庫

這期內容當中小編將會給大家帶來有關如何理解InnoDB引擎,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

一、綜述

innodb的物理文件包括系統表空間文件ibdata,用戶表空間文件ibd,日志文件ib_logfile,臨時表空間文件ibtmp,undo獨立表空間等。

  • 系統表空間是innodb最重要的文件,它記錄包括元數據信息,事務系統信息,ibuf信息,double write等關鍵信息。

  • 用戶表空間文件通常分為兩類,一類是當innodb_file_per_table打開時,一個用戶表空間對應一個文件,另外一種則是5.7版本引入的所謂General Tablespace,在滿足一定約束條件下,可以將多個表創建到同一個文件中。

  • 日志文件主要用于記錄redo log。innodb在所有數據變更前,先寫redo日志。為保證redo日志原子寫入,日志通常以512字節的block單位寫入。但由于現代文件系統升級,block_size通常設置到了4k,因此innodb也提供了一個選項支持redo日志以4k為單位寫入。

  • 臨時表空間文件用于存儲所有非壓縮的臨時表,第1~32個臨時表專用的回滾段也存放在該文件中。由于臨時表的本身屬性,該文件在重啟時會重新創建。

  • undo獨立表空間是innodb的一個可選項,由innodb_undo_tablespaces配置。默認情況下,該值為0,即undo數據是存儲在ibdata中。innodb_undo_tablespaces 設置為非0,可使得undo 回滾段分配到不同的文件中,目前開啟undo tablespace 只能在install階段進行。

上述文件除日志文件外,都具有較為統一的物理結構。所有物理文件由頁(page 或 block)構成,在未被壓縮情況下,一個頁的大小為UNIV_PAGE_SIZE(16384,16K)。不同用途的頁具有相同格式的頁頭(38)和頁尾(8),其中記錄了頁面校驗值,頁面編號,表空間編號,LSN等通用信息,詳見下表。所有page通過一定方式組織起來,下面我們分別從物理結構,邏輯結構,文件管理過程來具體了解innodb的文件結構。

如何理解InnoDB引擎

二、文件物理結構

2.1 基本物理結構

innodb 的每個數據文件都歸屬于一個表空間(tablespace),不同的表空間使用一個唯一標識的space id來標記。值得注意的是,系統表空間ibdata雖然包括不同文件ibdata1, ibdata2…,但這些文件邏輯上是相連的,這些文件同屬于space_id為0的表空間。

表空間內部,所有頁按照區(extent)為物理單元進行劃分和管理。extent內所有頁面物理相鄰。對于不同的page size,對應的extent大小也不同,對應為:

如何理解InnoDB引擎

通常情況下,extent由64個物理連續的頁組成,表空間可以理解為由一個個物理相鄰的extent組成。為了組織起這些extent,每個extent都有一個占40字節的XDES entry。利用XDES entry,我們可以方便地了解到該extent每頁空閑與否,以及其當前狀態。其格式如下:

如何理解InnoDB引擎

所有XDES entry都統一放在extent描述頁中,一個extent描述頁至多存放256個XDES entry,用于管理其隨后物理相鄰的256個extent(256*64 = 16384 page),如下圖所示所示:

如何理解InnoDB引擎由圖可見,每個XDES entry有嚴格對應的頁面,其對應頁面上下界可以描述為:

min_scope = extent 描述頁 page_no + xdes 編號 * 64
max_scope =( extent 描述頁 page_no + xdes 編號 * 64 )+63

值得注意的是,其中 page 0的extent描述頁還記錄了與該table space相關的信息(FSP HEADER),其類型為FIL_PAGE_TYPE_FSP_HDR。其他extent描述頁的類型相同,為FIL_PAGE_TYPE_XDES。

2.2 系統數據頁

系統表空間(ibdata)不僅存放了SYS_TABLE / SYS_INDEX 等系統表的數據,還存放了回滾信息(undo),插入緩沖索引頁(IBUF bitmap),系統事務信息(trx_sys),二次寫緩沖(double write)等信息。
innodb中核心的數據都存放在ibdata中的系統數據頁中。系統數據頁主要包括:FIL_PAGE_TYPE_FSP_HDR, FIL_PAGE_IBUF_BITMAP, FIL_PAGE_TYPE_SYS, IBUF_ROOT_PAGE, FIL_PAGE_TYPE_TRX_SYS, FIL_PAGE_TYPE_SYS, DICT_HDR_PAGE等。

  • FIL_PAGE_TYPE_FSP_HDR/FIL_PAGE_TYPE_XDES
    extent描述頁(page 0/16384/32768/… ),上文已述及,故不再展開。

  • FIL_PAGE_IBUF_BITMAP
    ibdata第2個page類型為FIL_PAGE_IBUF_BITMAP,主要用于跟蹤隨后的每個page的change buffer信息。由于bitmap page的空間有限,同樣每隔256個extent Page之后,也會在XDES PAGE之后創建一個ibuf bitmap page。

  • FIL_PAGE_INODE
    ibdata的第3個page的類型為FIL_PAGE_INODE,用于管理數據文件中的segment,每個inode頁可以存儲FSP_SEG_INODES_PER_PAGE(默認為85)個記錄。segment是表空間管理的邏輯單位,每個索引占用2個segment,分別用于管理葉子節點和非葉子節點。關于segment的詳細介紹,將在第三節展開。

  • FSP_IBUF_HEADER_PAGE_NO 和 FSP_IBUF_TREE_ROOT_PAGE_NO
    上述兩個頁分別是Ibdata的第4個page和第5個page。change buffer本質上也是btree結構,其root頁固定在第5個page FSP_IBUF_TREE_ROOT_PAGE_NO。由于FSP_IBUF_TREE_ROOT_PAGE_NO中原先用于記錄leaf inode entry的字段被用于維護空閑page鏈表了,因此ibdata需要使用第4頁FSP_IBUF_TREE_ROOT_PAGE_NO 來對ibuf進行空間管理。

  • FSP_TRX_SYS_PAGE_NO
    ibdata第6個page的類型為FSP_TRX_SYS_PAGE_NO,記錄了innodb重要的事務系統信息,包括持久化的最大事務ID,以及128個rseg(rollback segment)的地址,double write位置等。這128個rseg中,rseg0固定在ibdata中,rseg1-rseg32用于管理臨時表,rseg33-rseg128 當未開啟undo獨立表空間 (innodb undo tablespace = 0)時,仍放在ibdata中,否則放在undo獨立表空間中。每個rseg中記錄了1024個slot,每個slot也都可對應一個事務,用于管理該事務的undo記錄。由于每個slot也需要申請和釋放page,因此每個slot也對應一個segment(空間管理邏輯單位)。

  • FSP_DICT_HDR_PAGE_NO
    ibdata第8個page的類型為FSP_DICT_HDR_PAGE_NO,用來存儲數據詞典表的信息 。該頁存儲了SYS_TABLES,SYS_TABLE_IDS,SYS_COLUMNS,SYS_INDEXES和SYS_FIELDS的root page,以及當前最大的TABLE_ID/ROW_ID/INDEX_ID/SPACE_ID。當對用戶表操作時,需要先從數據字典表中獲取到用戶表對應的表空間,以及其索引root頁的page_no,才能定位到具體數據的位置,對其進行增刪改查。(只有拿到數據詞典表,才能根據其中存儲的表信息,進一步找到其對應的表空間,以及表的聚集索引所在的page no)

  • double write buffer
    innodb使用double write buffer來防止數據頁的部分寫問題,在寫一個數據頁之前,總是先寫double write buffer,再寫數據文件。當崩潰恢復時,如果數據文件中page損壞,會嘗試從dblwr中恢復。double write buffer總共128個page,劃分為兩個block。由于dblwr在安裝實例時已經初始化好了,這兩個block在Ibdata中具有固定的位置,page64 ~127 劃屬第一個block,page 128 ~191劃屬第二個block。

當innodb_file_per_table為off狀態時,所有用戶表也將和SYS_TABLE / SYS_INDEX 等系統表一樣,存儲在ibdata中。當開啟innodb_file_per_table時,innodb會為每一個用戶表建立一個獨立的ibd文件。該ibd文件存放了對應用戶表的索引數據和插入緩沖bitmap。而該表的回滾數據(undo)仍記錄在ibdata中。

三、文件邏輯結構

3.1 基本邏輯結構

innodb為了組織各extent,在表空間的第一個page還維護了三個extent的鏈表:FSP_FREE、FSP_FREE_FRAG、FSP_FULL_FRAG。分別將extent完全未被使用,部分被使用,完全被使用的Xdes entry串聯起來。如下圖所示:

如何理解InnoDB引擎

段(segment 或稱 inode)是用來管理物理文件的邏輯單位,可以向表空間申請分配和釋放page或extent,是構成索引,回滾段的基本元素。為節省空間,每個segment都先從表空間FREE_FRAG中分配32個頁(FSEG_FRAG_ARR),當這些32個頁面不夠使用時。按照以下原則進行擴展:如果當前小于1個extent,則擴展到1個extent滿;當表空間小于32MB時,每次擴展一個extent;大于32MB時,每次擴展4個extent。
在為segment分配空閑的extent時,如果表空間FSP_FREE上沒有空閑的extent,則會為FSP_FREE重新初始化一些空閑extent。extent的分配類似于實現了一套借還機制。segment向表空間租借extent,只有segment退還該空間時,該extent才能重新出現在FSP_FREE/FSP_FULL_FRAG/FSP_FULL中。
segment內部為了管理起這些分配來的extent。也有三個extent鏈表:FSEG_FREE、FSEG_NOT_FULL、FSEG_FULL,也分別對應extent完全未被使用,部分被使用,完全被使用的Xdes entry。segment的結構如下圖所示

如何理解InnoDB引擎inode entry是用于管理segment的結構,一個inode entry對應一個segment。segment的32個頁(FSEG_FRAG_ARR),FSEG_FREE、FSEG_NOT_FULL、FSEG_FULL等信息都記錄在inode entry中。inode entry的具體結構如下表所示:

如何理解InnoDB引擎inode entry所在的inode page有可能存放滿,因此又通過頭page(FIL_PAGE_TYPE_FSP_HDR)中維護了兩個inode Page鏈表FSP_SEG_INODES_FULL和FSP_SEG_INODES_FREE。前者對應沒有空閑inode entry的inode page鏈表,后者對應的至少有一個空閑inode entry的inode page鏈表,如下圖所示:

如何理解InnoDB引擎

3.2 索引

ibd文件中真正構建起用戶數據的結構是btree。表中的每一個索引對應一個btree。主鍵(cluster index)對應btree的葉子節點上記錄了行的全部列數據(加上transaction id列及rollback ptr)。當表中無主鍵時,innodb會為該表每一行分配一個唯一的rowID,并基于它構造btree。如果表中存在二級索引(secondary index),那么其btree葉子節點存儲了鍵值加上cluster index索引鍵值。
每個btree使用兩個Segment來管理數據頁,一個管理葉子節點(leaf segment),一個管理非葉子節點(non-leaf segment)。這兩個segment的inode entry地址記錄在btree的root page中。root page分配在non-leaf segment第一個碎片頁上(FSEG_FRAG_ARR)。
當對一個表進行增刪改查的操作時,我們首先需要從ibdata的第8頁FSP_DICT_HDR_PAGE_NO中load改表的元數據信息,從SYS_INDEXES表中獲取該表各索引對應的root page no,進而通過root page對這個表的用戶數據btree進行操作。表空間的邏輯結構如下圖所示:

如何理解InnoDB引擎

3.3 索引頁數據

索引最基本的頁類型為FIL_PAGE_INDEX,其結構如下表所示。Index Header中記錄了page所在btree層次,所屬index ID,page directory槽數等與頁面相關的信息。Fseg Header中記錄了該index的leaf-segment和non-leaf segment的inode entry,system records包括infimum和supremum,分別代表該頁最小、最大記錄虛擬記錄。page directory是頁內記錄的索引。btree只能檢索到記錄所在的page,page內的檢索需要使用到通過page directory構建起的二分查找。

如何理解InnoDB引擎

innodb按行存放數據。當前MySQL支持等行格式包括antelope(compact和redundant),和barracuda(dynamic和compressed)。barracuda與antelope主要區別在于其處理行外數據等方式,barracuda只存儲行外數據等地址指針,不像antelope一樣存放768字節的行前綴內容。以compact行格式為例介紹行格式的具體內容,如下圖所示,行由變長字段長度列表、NULL標志位、記錄頭信息、系統列、用戶列組成。記錄頭信息中存放刪除標志、列總數、下行相對偏移等信息、系統列包括rowID、transactionID、rollback pointer等組成。

如何理解InnoDB引擎

四、文件管理過程

下面用精簡后的源碼來簡單介紹innodb文件的管理過程。

4.1 btree的創建過程

btree的創建過程可以概括為:先創建non_leaf segment,利用non_leaf segment的首頁(即32個碎片頁中第一頁)作為root page;然后創建leaf_segment;最后對root page進行必要的初始化。詳細過程請參考以下代碼:

btr_create(
    ulint type,
    ulint space,
    const page_size_t& page_size,
    index_id_t index_id,
    dict_index_t* index,
    const btr_create_t* btr_redo_create_info,
    mtr_t* mtr)
{
    /* index tree 的segment headers 存儲于新分配的root page中,ibuf tree的
    segment headers放在獨立的ibuf header page中。以下代碼屏蔽了ibuf tree的
    創建邏輯,重點介紹index tree的創建過程 */
    /* 局部變量 */
    ...
    /* 創建一個non_leaf segment段,并將段的地址存儲到段首頁偏移為
    PAGE_HEADER + PAGE_BTR_SEG_TOP的位置,用block記錄下non_leaf segment
    段首頁page對應的block,該block將作為該btree的root page */
    block = fseg_create(space, 0,
    PAGE_HEADER + PAGE_BTR_SEG_TOP, mtr);
    if (block == NULL) {
        return(FIL_NULL);
    }
    /* 記錄下root page的信息 */
    page_no = block->page.id.page_no();
        frame = buf_block_get_frame(block);
        /* 創建leaf_segment,并將段首存儲到root page上偏移為
        PAGE_HEADER + PAGE_BTR_SEG_LEAF的位置 */
        if (!fseg_create(space, page_no,
            PAGE_HEADER + PAGE_BTR_SEG_LEAF, mtr)) {
            /* 沒有足夠的空間分配新的segment,需要釋放掉已分配的root page */
            btr_free_root(block, mtr);
            return(FIL_NULL);
        }
        /* 在root page上做index page的初始化,根據頁面壓縮與否做不同處理 */
        page_zip = buf_block_get_page_zip(block);
        if (page_zip) {
            /* 其他邏輯 */
            page = page_create_zip(block, index, 0, 0, NULL, mtr);
        } else {
            /* 其他邏輯 */
            page = page_create(block, mtr,
            dict_table_is_comp(index->table),
            dict_index_is_spatial(index));
        }
        /* 在root page上設置其所在的index id */
        btr_page_set_index_id(page, page_zip, index_id, mtr);
        /* 將root page的前后頁面設置為NULL */
        btr_page_set_next(page, page_zip, FIL_NULL, mtr);
        btr_page_set_prev(page, page_zip, FIL_NULL, mtr);
        /* 其他邏輯 */
        /* 返回root page的頁面號 */
        return(page_no);
}

4.2 segment的創建過程

segment的創建過程比較簡單:先在inode page中為segment分配一個inode entry,然后再inode entry上進行初始化,更新space header里的最大segment id,即可。需要注意的是:當傳入的page 為0 時,意味著要創建一個獨立的segment,需要將當前的inode entry地址記錄在段首page中,并返回;當傳入的page非0時,segment需要在指定的page的指定位置記錄下當前的inode entry地址。詳細過程請參考代碼:

buf_block_t*
fseg_create_general(
/*================*/
    ulint    space_id,/*!< in: space id */
    ulint    page,    /*!< in: page where the segment header is placed: if
            this is != 0, the page must belong to another segment,
            if this is 0, a new page will be allocated and it
            will belong to the created segment */
    ulint    byte_offset, /*!< in: byte offset of the created segment header
            on the page */
    ibool    has_done_reservation, /*!< in: TRUE if the caller has already
            done the reservation for the pages with
            fsp_reserve_free_extents (at least 2 extents: one for
            the inode and the other for the segment) then there is
            no need to do the check for this individual
            operation */
    mtr_t*    mtr)    /*!< in/out: mini-transaction */
{
    /* 局部變量 */
    ...
    /* 如果傳入的page是0,則創建一個獨立的段,并把segment header的信息
    存儲在段首page中。如果傳入page是非0,則這是一個非獨立段,需要將
    segment header的信息存儲在指定page的指定位置上 */
    if (page != 0) {
        /* 獲取指定page */
        block = buf_page_get(page_id_t(space_id, page), page_size,
                     RW_SX_LATCH, mtr);
        header = byte_offset + buf_block_get_frame(block);
    }
    /* 其他邏輯 */
    /* 獲取space header和inode_entry */
    space_header = fsp_get_space_header(space_id, page_size, mtr);
    inode = fsp_alloc_seg_inode(space_header, mtr);
    if (inode == NULL) {
        goto funct_exit;
    }
    /* 獲取當前表空間最大segment id,并更新表空間最大
    segment id */
    seg_id = mach_read_from_8(space_header + FSP_SEG_ID);
    mlog_write_ull(space_header + FSP_SEG_ID, seg_id + 1, mtr);
    /* 初始化inode entry的segment id 和 FSEG_NOT_FULL_N_USED */
    mlog_write_ull(inode + FSEG_ID, seg_id, mtr);
    mlog_write_ulint(inode + FSEG_NOT_FULL_N_USED, 0, MLOG_4BYTES, mtr);
    /* 初始化inode entry的三個extent鏈表 */
    flst_init(inode + FSEG_FREE, mtr);
    flst_init(inode + FSEG_NOT_FULL, mtr);
    flst_init(inode + FSEG_FULL, mtr);
    /* 初始化innode entry的32個碎片頁 */
    mlog_write_ulint(inode + FSEG_MAGIC_N, FSEG_MAGIC_N_VALUE,
             MLOG_4BYTES, mtr);
    for (i = 0; i < FSEG_FRAG_ARR_N_SLOTS; i++) {
        fseg_set_nth_frag_page_no(inode, i, FIL_NULL, mtr);
    }
    /* 如果傳入的page是0,則分配一個段首page */
    if (page == 0) {
        block = fseg_alloc_free_page_low(space, page_size,
                         inode, 0, FSP_UP, RW_SX_LATCH,
                         mtr, mtr
#ifdef UNIV_DEBUG
                         , has_done_reservation
#endif /* UNIV_DEBUG */
                         );
        header = byte_offset + buf_block_get_frame(block);
        mlog_write_ulint(buf_block_get_frame(block) + FIL_PAGE_TYPE,
                 FIL_PAGE_TYPE_SYS, MLOG_2BYTES, mtr);
    }
    /* 在page指定位置記錄segment header,segment header由
    inode page所在的space id,page no, 以及inode entry的在
    inode page 中的頁內偏移組成 */
    mlog_write_ulint(header + FSEG_HDR_OFFSET,
             page_offset(inode), MLOG_2BYTES, mtr);
    mlog_write_ulint(header + FSEG_HDR_PAGE_NO,
             page_get_page_no(page_align(inode)),
             MLOG_4BYTES, mtr);
    mlog_write_ulint(header + FSEG_HDR_SPACE, space_id, MLOG_4BYTES, mtr);
funct_exit:
    DBUG_RETURN(block);
}

4.3 extent的分配過程

表空間分配extent的邏輯比較簡單,直接查詢FSP_FREE上有沒有剩余的extent即可,沒有的話就為FSP_FREE重新初始化一些extent。詳細邏輯如下:

static
xdes_t*
fsp_alloc_free_extent(
    ulint            space_id,
    const page_size_t&    page_size,
    ulint            hint,
    mtr_t*            mtr)
{
    /* 局部變量 */
    ...
    /* 獲取space header */
    header = fsp_get_space_header(space_id, page_size, mtr);
    /* 獲取hint頁所在的xdes entry */
    descr = xdes_get_descriptor_with_space_hdr(
        header, space_id, hint, mtr, false, &desc_block);
    fil_space_t*    space = fil_space_get(space_id);
    /* 當hint頁所在的xdes entry的狀態是XDES_FREE時,直接將其摘下返回,
    否則嘗試從FSP_FREE中為segment分配extent。如果FSP_FREE為空,
    則需要進一步從未初始化的空間中為FSP_FREE新分配一些extent,
    并從新的FSP_FREE中取出第一個extent返回 */
    if (descr && (xdes_get_state(descr, mtr) == XDES_FREE)) {
        /* Ok, we can take this extent */
    } else {
        /* Take the first extent in the free list */
        first = flst_get_first(header + FSP_FREE, mtr);
        if (fil_addr_is_null(first)) {
            fsp_fill_free_list(false, space, header, mtr);
            first = flst_get_first(header + FSP_FREE, mtr);
        }
        /* 分配失敗 */
        if (fil_addr_is_null(first)) {
            return(NULL);    /* No free extents left */
        }
        descr = xdes_lst_get_descriptor(
            space_id, page_size, first, mtr);
    }
    /* 將分配到的extent從FSP_FREE中刪除 */
    flst_remove(header + FSP_FREE, descr + XDES_FLST_NODE, mtr);
    space->free_len--;
    return(descr);
}

當為segment分配extent時稍微復雜一些:先檢查FSEG_FREE中是否有剩余的extent,如果沒有再用fsp_alloc_free_extent從表空間中申請extent。在第二種情況下,FSEG_FREE中的extent不足,因此還會進一步嘗試為FSEG_FREE分配更多extent。詳細過程如下:

static
xdes_t*
fseg_alloc_free_extent(
    fseg_inode_t*        inode,
    ulint            space,
    const page_size_t&    page_size,
    mtr_t*            mtr)
{
    /* 局部變量 */
    ...
    /* 如果FSEG_FREE非空,則從其中為segment分配extent,如果FSEG_FREE為空,
    則從調用fsp_alloc_free_extent 為當前segment分配extent */
    if (flst_get_len(inode + FSEG_FREE) > 0) {
        first = flst_get_first(inode + FSEG_FREE, mtr);
        descr = xdes_lst_get_descriptor(space, page_size, first, mtr);
    } else {
        descr = fsp_alloc_free_extent(space, page_size, 0, mtr);
        if (descr == NULL) {
            return(NULL);
        }
        /* 將從space申請到的extent設置為segment私有狀態(XDES_FSEG),
        將改extent加入到FSEG_FREE中 */
        seg_id = mach_read_from_8(inode + FSEG_ID);
        xdes_set_state(descr, XDES_FSEG, mtr);
        mlog_write_ull(descr + XDES_ID, seg_id, mtr);
        flst_add_last(inode + FSEG_FREE, descr + XDES_FLST_NODE, mtr);
        /* 當前FSEP_FREE中剩余的extent不多,嘗試為當前segment分配更多
        物理相鄰的extent */
        fseg_fill_free_list(inode, space, page_size,
                    xdes_get_offset(descr) + FSP_EXTENT_SIZE,
                    mtr);
    }
    return(descr);
}

4.4 page的分配過程

表空間page的分配過程如下:先查看hint_page所在的extent是否適合分配空閑頁面,不適合的話,則嘗試從FSP_FREE_FRAG鏈表中尋找空閑頁面。如果FSP_FREE_FRAG為空,則新分配一個extent,將其添加到FSP_FREE_FRAG中,并在其中分配空閑頁面。

static MY_ATTRIBUTE((warn_unused_result))
buf_block_t*
fsp_alloc_free_page(
    ulint            space,
    const page_size_t&    page_size,
    ulint            hint,
    rw_lock_type_t        rw_latch,
    mtr_t*            mtr,
    mtr_t*            init_mtr)
{
    /* 局部變量 */
    ...
    /* 獲取表空間header 和 hint page所在extent的xdes entry */
    header = fsp_get_space_header(space, page_size, mtr);
    descr = xdes_get_descriptor_with_space_hdr(header, space, hint, mtr);
    /* 如果xdes entry的狀態是XDES_FREE_FRAG,那就直接從該extent中分配page,
    否則從FSP_FREE_FRAG中去尋找空閑page */
    if (descr && (xdes_get_state(descr, mtr) == XDES_FREE_FRAG)) {
        /* Ok, we can take this extent */
    } else {
        /* Else take the first extent in free_frag list */
        first = flst_get_first(header + FSP_FREE_FRAG, mtr);
        /* 嘗試從FSP_FREE_FRAG中尋找空閑頁面,當FSP_FREE_FRAG鏈表為空時,
        需要使用fsp_alloc_free_extent分配一個新的extent,將該extent加入
        FSP_FREE_FRAG,并在其中分配空閑page */
        if (fil_addr_is_null(first)) {
            descr = fsp_alloc_free_extent(space, page_size,
                              hint, mtr);
            if (descr == NULL) {
                /* No free space left */
                return(NULL);
            }
            xdes_set_state(descr, XDES_FREE_FRAG, mtr);
            flst_add_last(header + FSP_FREE_FRAG,
                      descr + XDES_FLST_NODE, mtr);
        } else {
            descr = xdes_lst_get_descriptor(space, page_size,
                            first, mtr);
        }
        /* Reset the hint */
        hint = 0;
    }
    /* 從找到的extent中分配一個空閑頁面 */
    free = xdes_find_bit(descr, XDES_FREE_BIT, TRUE,
                 hint % FSP_EXTENT_SIZE, mtr);
    if (free == ULINT_UNDEFINED) {
        ut_print_buf(stderr, ((byte*) descr) - 500, 1000);
        putc('
', stderr);
        ut_error;
    }
    page_no = xdes_get_offset(descr) + free;
    /* 其他邏輯 */
    /* 在fsp_alloc_from_free_frag中設置分配page的XDES_FREE_BIT為false,
    表示被占用;遞增頭page的FSP_FRAG_N_USED字段;如果該extent被用滿了,
    就將其從FSP_FREE_FRAG移除,并加入到FSP_FULL_FRAG鏈表中,更新FSP_FRAG_N_USED的值 */
    fsp_alloc_from_free_frag(header, descr, free, mtr);
    /* 對Page內容進行初始化后返回 */
    return(fsp_page_create(page_id_t(space, page_no), page_size,
                   rw_latch, mtr, init_mtr));
}

為了能夠使得segment內邏輯上相鄰的節點在物理上也盡量相鄰,盡量提高表空間的利用率,在segment中分配page的邏輯較為復雜。詳細過程如下所述:

static
buf_block_t*
fseg_alloc_free_page_low(
    fil_space_t*        space,
    const page_size_t&    page_size,
    fseg_inode_t*        seg_inode,
    ulint            hint,
    byte            direction,
    rw_lock_type_t        rw_latch,
    mtr_t*            mtr,
    mtr_t*            init_mtr
#ifdef UNIV_DEBUG
    , ibool            has_done_reservation
#endif /* UNIV_DEBUG */
)
{
    /* 局部變量 */
    ...
    /* 計算當前segment使用的和占用的page數。前者統計的統計方法為
    累加32個碎片頁中已使用的數量,FSEG_FULL/FSEG_NOT_FULL中已使
    用page的數量,后者的統計方法為累加32個碎片頁已使用數量,
    FSEG_FULL/FSEG_NOT_FULL/FSEG_FREE三個鏈表中總page數*/
    reserved = fseg_n_reserved_pages_low(seg_inode, &used, mtr);
    /* 獲取表空間header 和 hint page所在extent的xdes entry */
    space_header = fsp_get_space_header(space_id, page_size, mtr);
    descr = xdes_get_descriptor_with_space_hdr(space_header, space_id,
                           hint, mtr);
    if (descr == NULL) {
        /* 說明hint page在free limit之外,將hint page置0,取消hint page的作用*/
        hint = 0;
        descr = xdes_get_descriptor(space_id, hint, page_size, mtr);
    }
    /* In the big if-else below we look for ret_page and ret_descr */
    /*-------------------------------------------------------------*/
    if ((xdes_get_state(descr, mtr) == XDES_FSEG)
        && mach_read_from_8(descr + XDES_ID) == seg_id
        && (xdes_mtr_get_bit(descr, XDES_FREE_BIT,
                 hint % FSP_EXTENT_SIZE, mtr) == TRUE)) {
take_hinted_page:
        /* 1. hint page所在的extent屬于當前segment,并且
        hint page也是空閑狀態,這是最理想的情況 */
        ret_descr = descr;
        ret_page = hint;
        goto got_hinted_page;
        /*-----------------------------------------------------------*/
    } else if (xdes_get_state(descr, mtr) == XDES_FREE
           && reserved - used < reserved / FSEG_FILLFACTOR
           && used >= FSEG_FRAG_LIMIT) {
        /* 2. segment空間利用率高于臨界值(7/8 ,FSEG_FILLFACTOR),
        并且hint page所在的extent處于XDES_FREE狀態,直接將該extent從
        FSP_FREE摘下,分配至segment的FSEG_FREE中,返回hint page */
        ret_descr = fsp_alloc_free_extent(
            space_id, page_size, hint, mtr);
        xdes_set_state(ret_descr, XDES_FSEG, mtr);
        mlog_write_ull(ret_descr + XDES_ID, seg_id, mtr);
        flst_add_last(seg_inode + FSEG_FREE,
                  ret_descr + XDES_FLST_NODE, mtr);
        /* 在利用率條件允許的情況下,為segment的FSEG_FREE多分配幾個
        物理相鄰的extent */
        fseg_fill_free_list(seg_inode, space_id, page_size,
                    hint + FSP_EXTENT_SIZE, mtr);
        goto take_hinted_page;
        /*-----------------------------------------------------------*/
    } else if ((direction != FSP_NO_DIR)
           && ((reserved - used) < reserved / FSEG_FILLFACTOR)
           && (used >= FSEG_FRAG_LIMIT)
           && (!!(ret_descr
              = fseg_alloc_free_extent(
                  seg_inode, space_id, page_size, mtr)))) {
        /* 3. 當利用率小于臨界值,不建議分配新的extent,避免空間浪費,
        此時從FSEG_FREE中獲取空閑extent,用于分配新的page */
        ret_page = xdes_get_offset(ret_descr);
        if (direction == FSP_DOWN) {
            ret_page += FSP_EXTENT_SIZE - 1;
        }
    } else if ((xdes_get_state(descr, mtr) == XDES_FSEG)
           && mach_read_from_8(descr + XDES_ID) == seg_id
           && (!xdes_is_full(descr, mtr))) {
        /* 4. 當hint page所在的extent屬于當前segment時,該extent內如有空閑page,
        將其返回 */
        ret_descr = descr;
        ret_page = xdes_get_offset(ret_descr)
            + xdes_find_bit(ret_descr, XDES_FREE_BIT, TRUE,
                    hint % FSP_EXTENT_SIZE, mtr);
    } else if (reserved - used > 0) {
        /* 5. 如果該segment占用的page數大于實用的page數,說明該segment還有空
        閑的page,則依次先看FSEG_NOT_FULL鏈表上是否有未滿的extent,如果沒有,
        再看FSEG_FREE鏈表上是否有完全空閑的extent */
        fil_addr_t    first;
        if (flst_get_len(seg_inode + FSEG_NOT_FULL) > 0) {
            first = flst_get_first(seg_inode + FSEG_NOT_FULL,
                           mtr);
        } else if (flst_get_len(seg_inode + FSEG_FREE) > 0) {
            first = flst_get_first(seg_inode + FSEG_FREE, mtr);
        } else {
            return(NULL);
        }
        ret_descr = xdes_lst_get_descriptor(space_id, page_size,
                            first, mtr);
        ret_page = xdes_get_offset(ret_descr)
            + xdes_find_bit(ret_descr, XDES_FREE_BIT, TRUE,
                    0, mtr);
    } else if (used < FSEG_FRAG_LIMIT) {
        /* 6. 當前segment的32個碎片頁尚未使用完畢,使用fsp_alloc_free_page從
        表空間FSP_FREE_FRAG中分配獨立的page,并加入到該inode的frag array page
        數組中 */
        buf_block_t* block = fsp_alloc_free_page(
            space_id, page_size, hint, rw_latch, mtr, init_mtr);
        if (block != NULL) {
            /* Put the page in the fragment page array of the
            segment */
            n = fseg_find_free_frag_page_slot(seg_inode, mtr);
            fseg_set_nth_frag_page_no(
                seg_inode, n, block->page.id.page_no(),
                mtr);
        }
        return(block);
    } else {
        /* 7. 當上述情況都不滿足時,直接使用fseg_alloc_free_extent分配一個空閑
        extent,并從其中取一個page返回 */
        ret_descr = fseg_alloc_free_extent(seg_inode,
                           space_id, page_size, mtr);
        if (ret_descr == NULL) {
            ret_page = FIL_NULL;
            ut_ad(!has_done_reservation);
        } else {
            ret_page = xdes_get_offset(ret_descr);
        }
    }
    /* page分配失敗 */
    if (ret_page == FIL_NULL) {
        return(NULL);
    }
got_hinted_page:
    /* 將可用的hint page標記為used狀態 */
    if (ret_descr != NULL) {
        fseg_mark_page_used(seg_inode, ret_page, ret_descr, mtr);
    }
    /* 對Page內容進行初始化后返回 */
    return(fsp_page_create(page_id_t(space_id, ret_page), page_size,
                   rw_latch, mtr, init_mtr));
}

innodb的文件結構由自下而上包括page(頁),extent(區),segment(段),tablespace(表空間)等幾個層次。page是最基本的物理單位,所有page具有相同的頁首和頁尾;extent由通常由連續的64個page組成,tablespace由一個個連續的extent組成;段是用來管理物理文件的邏輯單位,可以向表空間申請分配和釋放page 或 extent,是構成索引,回滾段的基本元素;表空間是一個宏觀概念,當innodb_file_per_table為ON時一個用戶表對應一個表空間。

上述就是小編為大家分享的如何理解InnoDB引擎了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

化德县| 凤城市| 仙游县| 海城市| 时尚| 平南县| 岳阳县| 阿坝| 抚松县| 仁布县| 吉林省| 宜州市| 石泉县| 南开区| 广州市| 怀化市| 伽师县| 油尖旺区| 大余县| 即墨市| 浦县| 淮阳县| 安徽省| 盘山县| 洛隆县| 波密县| 岳阳县| 长岭县| 文水县| 华宁县| 扬州市| 布尔津县| 南部县| 马公市| 兰溪市| 石泉县| 诸暨市| 裕民县| 南宁市| 克拉玛依市| 修武县|