## 綜述
從上層的角度來看,InnoDB層的文件,除了redo日志外,基本上具有相當統一的結構,都是固定block大小,普遍使用的btree結構來管理數據。只是針對不同的block的應用場景會分配不同的頁類型。通常默認情況下,每個block的大小為 UNIV_PAGE_SIZE,在不做任何配置時值為16kb,你還可以選擇在安裝實例時指定一個塊的block大小。對于壓縮表,可以在建表時指定block size,但在內存中表現的解壓頁依舊為統一的頁大小。
從物理文件的分類來看,有日志文件、主系統表空間文件ibdata、undo tablespace文件、臨時表空間文件、用戶表空間。
日志文件主要用于記錄redo log,InnoDB采用循環使用的方式,你可以通過參數指定創建文件的個數和每個文件的大小。默認情況下,日志是以512字節的block單位寫入。由于現代文件系統的block size通常設置到4k,InnoDB提供了一個選項,可以讓用戶將寫入的redo日志填充到4KB,以避免read-modify-write的現象;而Percona Server則提供了另外一個選項,支持直接將redo日志的block size修改成指定的值。
ibdata是InnoDB最重要的系統表空間文件,它記錄了InnoDB的核心信息,包括事務系統信息、元數據信息,記錄InnoDB change buffer的btree,防止數據損壞的double write buffer等等關鍵信息。我們稍后會展開描述。
undo獨立表空間是一個可選項,通常默認情況下,undo數據是存儲在ibdata中的,但你也可以通過配置選項?`innodb_undo_tablespaces`?來將undo 回滾段分配到不同的文件中,目前開啟undo tablespace 只能在install階段進行。在主流版本進入5.7時代后,我們建議開啟獨立undo表空間,只有這樣才能利用到5.7引入的新特效:online undo truncate。
MySQL 5.7 新開辟了一個臨時表空間,默認的磁盤文件命名為ibtmp1,所有非壓縮的臨時表都存儲在該表空間中。由于臨時表的本身屬性,該文件在重啟時會重新創建。對于云服務提供商而言,通過ibtmp文件,可以更好的控制臨時文件產生的磁盤存儲。
用戶表空間,顧名思義,就是用于自己創建的表空間,通常分為兩類,一類是一個表空間一個文件,另外一種則是5.7版本引入的所謂General Tablespace,在滿足一定約束條件下,可以將多個表創建到同一個文件中。除此之外,InnoDB還定義了一些特殊用途的ibd文件,例如全文索引相關的表文件。而針對空間數據類型,也構建了不同的數據索引格式R-tree。
在關鍵的地方本文注明了代碼函數,建議讀者邊參考代碼邊閱讀本文,本文的代碼部分基于MySQL 5.7.11版本,不同的版本函數名或邏輯可能會有所不同。請讀者閱讀本文時盡量選擇該版本的代碼。
## 文件管理頁
InnoDB 的每個數據文件都歸屬于一個表空間,不同的表空間使用一個唯一標識的space id來標記。例如ibdata1, ibdata2… 歸屬系統表空間,擁有相同的space id。用戶創建表產生的ibd文件,則認為是一個獨立的tablespace,只包含一個文件。
每個文件按照固定的 page size 進行區分,默認情況下,非壓縮表的page size為16Kb。而在文件內部又按照64個Page(總共1M)一個Extent的方式進行劃分并管理。對于不同的page size,對應的Extent大小也不同,對應為:
| page size | file space extent size |
| --- | --- |
| 4 KiB | 256 pages = 1 MiB |
| 8 KiB | 128 pages = 1 MiB |
| 16 KiB | 64 pages = 1 MiB |
| 32 KiB | 64 pages = 2 MiB |
| 64 KiB | 64 pages = 4 MiB |
盡管支持更大的Page Size,但目前還不支持大頁場景下的數據壓縮,原因是這涉及到修改壓縮頁中slot的固定size(其實實現起來也不復雜)。在不做聲明的情況下,下文我們默認使用16KB的Page Size來闡述文件的物理結構。
為了管理整個Tablespace,除了索引頁外,數據文件中還包含了多種管理頁,如下圖所示,一個用戶表空間大約包含這些頁來管理文件,下面會一一進行介紹。

InnoDB 管理頁
### 文件鏈表
首先我們先介紹基于文件的一個基礎結構,即文件鏈表。為了管理Page,Extent這些數據塊,在文件中記錄了許多的節點以維持具有某些特征的鏈表,例如在在文件頭維護的inode page鏈表,空閑、用滿以及碎片化的Extent鏈表等等。
在InnoDB里鏈表頭稱為`FLST_BASE_NODE`,大小為`FLST_BASE_NODE_SIZE`(16個字節)。BASE NODE維護了鏈表的頭指針和末尾指針,每個節點稱為`FLST_NODE`,大小為`FLST_NODE_SIZE`(12個字節)。相關結構描述如下:
`FLST_BASE_NODE`:
| Macro | bytes | Desc |
| --- | --- | --- |
| FLST_LEN | 4 | 存儲鏈表的長度 |
| FLST_FIRST | 6 | 指向鏈表的第一個節點 |
| FLST_LAST | 6 | 指向鏈表的最后一個節點 |
`FLST_NODE`:
| Macro | bytes | Desc |
| --- | --- | --- |
| FLST_PREV | 6 | 指向當前節點的前一個節點 |
| FLST_NEXT | 6 | 指向當前節點的下一個節點 |
如上所述,文件鏈表中使用6個字節來作為節點指針,指針的內容包括:
| Macro | bytes | Desc |
| --- | --- | --- |
| FIL_ADDR_PAGE | 4 | Page No |
| FIL_ADDR_BYTE | 2 | Page內的偏移量 |
該鏈表結構是InnoDB表空間內管理所有page的基礎結構,下圖先感受下,具體的內容可以繼續往下閱讀。

InnoDB 表空間page管理
文件鏈表管理的相關代碼參閱:include/fut0lst.ic, fut/fut0lst.cc
### FSP_HDR PAGE
數據文件的第一個Page類型為`FIL_PAGE_TYPE_FSP_HDR`,在創建一個新的表空間時進行初始化(`fsp_header_init`),該page同時用于跟蹤隨后的256個Extent(約256MB文件大小)的空間管理,所以每隔256MB就要創建一個類似的數據頁,類型為`FIL_PAGE_TYPE_XDES`?,XDES Page除了文件頭部外,其他都和`FSP_HDR`頁具有相同的數據結構,可以稱之為Extent描述頁,每個Extent占用40個字節,一個XDES Page最多描述256個Extent。
`FSP_HDR`頁的頭部使用`FSP_HEADER_SIZE`個字節來記錄文件的相關信息,具體的包括:
| Macro | bytes | Desc |
| --- | --- | --- |
| FSP_SPACE_ID | 4 | 該文件對應的space id |
| FSP_NOT_USED | 4 | 如其名,保留字節,當前未使用 |
| FSP_SIZE | 4 | 當前表空間總的PAGE個數,擴展文件時需要更新該值(`fsp_try_extend_data_file_with_pages`) |
| FSP_FREE_LIMIT | 4 | 當前尚未初始化的最小Page No。從該Page往后的都尚未加入到表空間的FREE LIST上。 |
| FSP_SPACE_FLAGS | 4 | 當前表空間的FLAG信息,見下文 |
| FSP_FRAG_N_USED | 4 | FSP_FREE_FRAG鏈表上已被使用的Page數,用于快速計算該鏈表上可用空閑Page數 |
| FSP_FREE | 16 | 當一個Extent中所有page都未被使用時,放到該鏈表上,可以用于隨后的分配 |
| FSP_FREE_FRAG | 16 | FREE_FRAG鏈表的Base Node,通常這樣的Extent中的Page可能歸屬于不同的segment,用于segment frag array page的分配(見下文) |
| FSP_FULL_FRAG | 16 | Extent中所有的page都被使用掉時,會放到該鏈表上,當有Page從該Extent釋放時,則移回FREE_FRAG鏈表 |
| FSP_SEG_ID | 8 | 當前文件中最大Segment ID + 1,用于段分配時的seg id計數器 |
| FSP_SEG_INODES_FULL | 16 | 已被完全用滿的Inode Page鏈表 |
| FSP_SEG_INODES_FREE | 16 | 至少存在一個空閑Inode Entry的Inode Page被放到該鏈表上 |
在文件頭使用FLAG(對應上述`FSP_SPACE_FLAGS`)描述了創建表時的如下關鍵信息:
| Macro | Desc |
| --- | --- |
| FSP_FLAGS_POS_ZIP_SSIZE | 壓縮頁的block size,如果為0表示非壓縮表 |
| FSP_FLAGS_POS_ATOMIC_BLOBS | 使用的是compressed或者dynamic的行格式 |
| FSP_FLAGS_POS_PAGE_SSIZE | Page Size |
| FSP_FLAGS_POS_DATA_DIR | 如果該表空間顯式指定了data_dir,則設置該flag |
| FSP_FLAGS_POS_SHARED | 是否是共享的表空間,如5.7引入的General Tablespace,可以在一個表空間中創建多個表 |
| FSP_FLAGS_POS_TEMPORARY | 是否是臨時表空間 |
| FSP_FLAGS_POS_ENCRYPTION | 是否是加密的表空間,MySQL 5.7.11引入 |
| FSP_FLAGS_POS_UNUSED | 未使用的位 |
除了上述描述信息外,其他部分的數據結構和XDES PAGE(`FIL_PAGE_TYPE_XDES`)都是相同的,使用連續數組的方式,每個XDES PAGE最多存儲256個XDES Entry,每個Entry占用40個字節,描述64個Page(即一個Extent)。格式如下:
| Macro | bytes | Desc |
| --- | --- | --- |
| XDES_ID | 8 | 如果該Extent歸屬某個segment的話,則記錄其ID |
| XDES_FLST_NODE | 12(FLST_NODE_SIZE) | 維持Extent鏈表的雙向指針節點 |
| XDES_STATE | 4 | 該Extent的狀態信息,包括:XDES_FREE,XDES_FREE_FRAG,XDES_FULL_FRAG,XDES_FSEG,詳解見下文 |
| XDES_BITMAP | 16 | 總共16*8= 128個bit,用2個bit表示Extent中的一個page,一個bit表示該page是否是空閑的(XDES_FREE_BIT),另一個保留位,尚未使用(XDES_CLEAN_BIT) |
`XDES_STATE`表示該Extent的四種不同狀態:
| Macro | Desc |
| --- | --- |
| XDES_FREE(1) | 存在于FREE鏈表上 |
| XDES_FREE_FRAG(2) | 存在于FREE_FRAG鏈表上 |
| XDES_FULL_FRAG(3) | 存在于FULL_FRAG鏈表上 |
| XDES_FSEG(4) | 該Extent歸屬于ID為XDES_ID記錄的值的SEGMENT。 |
通過`XDES_STATE`信息,我們只需要一個`FLIST_NODE`節點就可以維護每個Extent的信息,是處于全局表空間的鏈表上,還是某個btree segment的鏈表上。
### IBUF BITMAP PAGE
第2個page類型為`FIL_PAGE_IBUF_BITMAP`,主要用于跟蹤隨后的每個page的change buffer信息,使用4個bit來描述每個page的change buffer信息。
| Macro | bits | Desc |
| --- | --- | --- |
| IBUF_BITMAP_FREE | 2 | 使用2個bit來描述page的空閑空間范圍:0(0 bytes)、1(512 bytes)、2(1024 bytes)、3(2048 bytes) |
| IBUF_BITMAP_BUFFERED | 1 | 是否有ibuf操作緩存 |
| IBUF_BITMAP_IBUF | 1 | 該Page本身是否是Ibuf Btree的節點 |
由于bitmap page的空間有限,同樣每隔256個Extent Page之后,也會在XDES PAGE之后創建一個ibuf bitmap page。
關于change buffer,這里我們不展開討論,感興趣的可以閱讀之前的這篇月報:
[MySQL · 引擎特性 · Innodb change buffer介紹](http://mysql.taobao.org/monthly/2015/07/01/)
### INODE PAGE
數據文件的第3個page的類型為`FIL_PAGE_INODE`,用于管理數據文件中的segement,每個索引占用2個segment,分別用于管理葉子節點和非葉子節點。每個inode頁可以存儲`FSP_SEG_INODES_PER_PAGE`(默認為85)個記錄。
| Macro | bits | Desc |
| --- | --- | --- |
| FSEG_INODE_PAGE_NODE | 12 | INODE頁的鏈表節點,記錄前后Inode Page的位置,BaseNode記錄在頭Page的FSP_SEG_INODES_FULL或者FSP_SEG_INODES_FREE字段。 |
| Inode Entry 0 | 192 | Inode記錄 |
| Inode Entry 1 | ? | ? |
| …… | ? | ? |
| Inode Entry 84 | ? | ? |
每個Inode Entry的結構如下表所示:
| Macro | bits | Desc |
| --- | --- | --- |
| FSEG_ID | 8 | 該Inode歸屬的Segment ID,若值為0表示該slot未被使用 |
| FSEG_NOT_FULL_N_USED | 8 | FSEG_NOT_FULL鏈表上被使用的Page數量 |
| FSEG_FREE | 16 | 完全沒有被使用并分配給該Segment的Extent鏈表 |
| FSEG_NOT_FULL | 16 | 至少有一個page分配給當前Segment的Extent鏈表,全部用完時,轉移到FSEG_FULL上,全部釋放時,則歸還給當前表空間FSP_FREE鏈表 |
| FSEG_FULL | 16 | 分配給當前segment且Page完全使用完的Extent鏈表 |
| FSEG_MAGIC_N | 4 | Magic Number |
| FSEG_FRAG_ARR 0 | 4 | 屬于該Segment的獨立Page。總是先從全局分配獨立的Page,當填滿32個數組項時,就在每次分配時都分配一個完整的Extent,并在XDES PAGE中將其Segment ID設置為當前值 |
| …… | …… | ? |
| FSEG_FRAG_ARR 31 | 4 | 總共存儲32個記錄項 |
#### 文件維護
從上文我們可以看到,InnoDB通過Inode Entry來管理每個Segment占用的數據頁,每個segment可以看做一個文件頁維護單元。Inode Entry所在的inode page有可能存放滿,因此又通過頭Page維護了Inode Page鏈表。
在ibd的第一個Page中還維護了表空間內Extent的FREE、`FREE_FRAG`、`FULL_FRAG`三個Extent鏈表;而每個Inode Entry也維護了對應的FREE、`NOT_FULL`、FULL三個Extent鏈表。這些鏈表之間存在著轉換關系,以高效的利用數據文件空間。
當創建一個新的索引時,實際上構建一個新的btree(`btr_create`),先為非葉子節點Segment分配一個inode entry,再創建root page,并將該segment的位置記錄到root page中,然后再分配leaf segment的Inode entry,并記錄到root page中。
當刪除某個索引后,該索引占用的空間需要能被重新利用起來。
創建Segment
首先每個Segment需要從ibd文件中預留一定的空間(`fsp_reserve_free_extents`),通常是2個Extent。但如果是新創建的表空間,且當前的文件小于1個Extent時,則只分配2個Page。
當文件空間不足時,需要對文件進行擴展(`fsp_try_extend_data_file`)。文件的擴展遵循一定的規則:如果當前小于1個Extent,則擴展到1個Extent滿;當表空間小于32MB時,每次擴展一個Extent;大于32MB時,每次擴展4個Extent(`fsp_get_pages_to_extend_ibd`)。
在預留空間后,讀取文件頭Page并加鎖(`fsp_get_space_header`),然后開始為其分配Inode Entry(`fsp_alloc_seg_inode`)。首先需要找到一個合適的inode page。
我們知道Inode Page的空間有限,為了管理Inode Page,在文件頭存儲了兩個Inode Page鏈表,一個鏈接已經用滿的inode page,一個鏈接尚未用滿的inode page。如果當前Inode Page的空間使用完了,就需要再分配一個inode page,并加入到`FSP_SEG_INODES_FREE`鏈表上(`fsp_alloc_seg_inode_page`)。對于獨立表空間,通常一個inode page就足夠了。
當拿到目標inode page后,從該Page中找到一個空閑(`fsp_seg_inode_page_find_free`)未使用的slot(空閑表示其不歸屬任何segment,即FSEG_ID置為0)。
一旦該inode page中的記錄用滿了,就從`FSP_SEG_INODES_FREE`鏈表上轉移到`FSP_SEG_INODES_FULL`鏈表。
獲得inode entry后,遞增頭page的`FSP_SEG_ID`,作為當前segment的seg id寫入到inode entry中。隨后進行一些列的初始化。
在完成inode entry的提取后,就將該inode entry所在inode page的位置及頁內偏移量存儲到其他某個page內(對于btree就是記錄在根節點內,占用10個字節,包含space id, page no, offset)。
Btree的根節點實際上是在創建non-leaf segment時分配的,root page被分配到該segment的frag array的第一個數組元素中。
Segment分配入口函數:?`fseg_create_general`
分配數據頁
隨著btree數據的增長,我們需要為btree的segment分配新的page。前面我們已經講過,segment是一個獨立的page管理單元,我們需要將從全局獲得的數據空間納入到segment的管理中。
Step 1:空間擴展
當判定插入索引的操作可能引起分裂時,會進行悲觀插入(`btr_cur_pessimistic_insert`),在做實際的分裂操作之前,會先對文件進行擴展,并嘗試預留(tree_height / 16 + 3)個Extent,大多數情況下都是3個Extent。
這里有個意外場景:如果當前文件還不超過一個Extent,并且請求的page數小于1/2個Extent時,則如果指定page數,保證有2個可用的空閑Page,或者分配指定的page,而不是以Extent為單位進行分配。
注意這里只是保證有足夠的文件空間,避免在btree操作時進行文件Extent。如果在這一步擴展了ibd文件(`fsp_try_extend_data_file`),新的數據頁并未初始化,也未加入到任何的鏈表中。
在判定是否有足夠的空閑Extent時,本身ibd預留的空閑空間也要納入考慮,對于普通用戶表空間是2個Extent + file_size * 1%。這些新擴展的page此時并未進行初始化,也未加入到,在頭page的`FSP_FREE_LIMIT`記錄的page no標識了這類未初始化頁的范圍。
Step 2:為segment分配page
隨后進入索引分裂階段(`btr_page_split_and_insert`),新page分配的上層調用棧:
~~~
btr_page_alloc
|--> btr_page_alloc_low
|--> fseg_alloc_free_page_general
|--> fseg_alloc_free_page_low
~~~
在傳遞的參數中,有個hint page no,通常是當前需要分裂的page no的前一個(direction = FSP_DOWN)或者后一個page no(direction = FSP_UP),其目的是將邏輯上相鄰的節點在物理上也盡量相鄰。
在Step 1我們已經保證了物理空間有足夠的數據頁,只是還沒進行初始化。將page分配到當前segment的流程如下(`fseg_alloc_free_page_low`):
1. 計算當前segment使用的和占用的page數
* 使用的page數存儲包括`FSEG_NOT_FULL`鏈表上使用的page數(存儲在inode entry的`FSEG_NOT_FULL_N_USED`中) + 已用滿segment的`FSEG_FULL`鏈表上page數 + 占用的frag array page數量;
* 占用的page數包括`FSEG_FREE`、`FSEG_NOT_FULL`、`FSEG_FULL`三個鏈表上的Extent + 占用的frag array page數量。
2. 根據hint page獲取對應的xdes entry (`xdes_get_descriptor_with_space_hdr`)
3. 當滿足如下條件時該hint page可以直接拿走使用:
* Extent狀態為`XDES_FSEG`,表示屬于一個segment
* hint page所在的Extent已被分配給當前segment(檢查xdes entry的XDES_ID)
* hint page對應的bit設置為free,表示尚未被占用
* 返回hint page
4. 當滿足條件:1) xdes entry當前是空閑狀態(XDES_FREE);2) 該segment中已使用的page數大于其占用的page數的7/8 (`FSEG_FILLFACTOR`);3) 當前segment已經使用了超過32個frag page,即表示其inode中的frag array可能已經用滿。
* 從表空間分配hint page所在的Extent (`fsp_alloc_free_extent`),將其從FSP_FREE鏈表上移除
* 設置該Extent的狀態為XDES_FSEG,寫入seg id,并加入到當前segment的FSEG_FREE鏈表中。
* 返回hint page
5. 當如下條件時:1) direction != FSP_NO_DIR,對于Btree分裂,要么FSP_UP,要么FSP_DOWN;2)已使用的空間小于已占用空間的7/8; 3)當前segment已經使用了超過32個frag page
* 嘗試從segment獲取一個Extent(`fseg_alloc_free_extent`),如果該segment的FSEG_FREE鏈表為空,則需要從表空間分配(`fsp_alloc_free_extent`)一個Extent,并加入到當前segment的FSEG_FREE鏈表上
* direction為FSP_DOWN時,返回該Extent最后一個page,為FSP_UP時,返回該Extent的第一個Page
6. xdes entry屬于當前segment且未被用滿,從其中取一個空閑page并返回
7. 如果該segment占用的page數大于實用的page數,說明該segment還有空閑的page,則依次先看`FSEG_NOT_FULL`鏈表上是否有未滿的Extent,如果沒有,再看FSEG_FREE鏈表上是否有完全空閑的Extent。從其中取一個空閑Page并返回
8. 當前已經實用的Page數小于32個page時,則分配獨立的page(`fsp_alloc_free_page`)并加入到該inode的frag array page數組中,然后返回該block
9. 當上述情況都不滿足時,直接分配一個Extent(`fseg_alloc_free_extent`),并從其中取一個page返回。
上述流程看起來比較復雜,但可以總結為:
1. 對于一個新的segment,總是優先填滿32個frag page數組,之后才會為其分配完整的Extent,可以利用碎片頁,并避免小表占用太多空間。
2. 盡量獲得hint page;
3. 如果segment上未使用的page太多,則盡量利用segment上的page。
上文提到兩處從表空間為segment分配數據頁,一個是分配單獨的數據頁,一個是分配整個Extent
表空間單獨數據頁的分配調用函數`fsp_alloc_free_page`:
1. 如果hint page所在的Extent在鏈表`XDES_FREE_FRAG`上,可以直接使用;否則從根據頭page的`FSP_FREE_FRAG`鏈表查看是否有可用的Extent;
2. 未能從上述找到一個可用Extent,直接分配一個Extent,并加入到`FSP_FREE_FRAG`鏈表中;
3. 從獲得的Extent中找到描述為空閑(`XDES_FREE_BIT`)的page。
4. 分配該page (`fsp_alloc_from_free_frag`)
* 設置page對應的bitmap的`XDES_FREE_BIT`為false,表示被占用;
* 遞增頭page的`FSP_FRAG_N_USED`字段;
* 如果該Extent被用滿了,就將其從`FSP_FREE_FRAG`移除,并加入到`FSP_FULL_FRAG`鏈表中。同時對頭Page的`FSP_FRAG_N_USED`遞減1個Extent(`FSP_FRAG_N_USED`只存儲未滿的Extent使用的page數量);
* 對Page內容進行初始化(`fsp_page_create`)。
表空間Extent的分配函數`fsp_alloc_free_extent`:
1. 通常先通過頭page看FSP_FREE鏈表上是否有空閑的Extent,如果沒有的話,則將新的Extent(例如上述step 1對文件做擴展產生的新page,從`FSP_FREE_LIMIT`算起)加入到`FSP_FREE`鏈表上(`fsp_fill_free_list`):
* 一次最多加4個Extent(`FSP_FREE_ADD`);
* 如果涉及到xdes page,還需要對xdes page進行初始化;
* 如果Extent中存在類似xdes page這樣的系統管理頁,這個Extent被加入到`FSP_FREE_FRAG`鏈表中而不是`FSP_FREE`鏈表;
* 取鏈表上第一個Extent為當前使用;
2. 將獲得的Extent從`FSP_FREE`移除,并返回對應的xdes entry(`xdes_lst_get_descriptor`)。
回收Page
數據頁的回收分為兩種,一種是整個Extent的回收,一種是碎片頁的回收。在刪除索引頁或者drop索引時都會發生。
當某個數據頁上的數據被刪光時,我們需要從其所在segmeng上刪除該page(`btr_page_free -->fseg_free_page --> fseg_free_page_low`),回收的流程也比較簡單:
1. 首先如果是該segment的frag array中的page,將對應的slot設置為FIL_NULL, 并返還給表空間(`fsp_free_page`):
* page在xdes entry中的狀態置為空閑;
* 如果page所在Extent處于`FSP_FULL_FRAG`鏈表,則轉移到`FSP_FREE_FRAG`中;
* 如果Extent中的page完全被釋放掉了,則釋放該Extent(`fsp_free_extent`),將其轉移到FSP_FREE鏈表;
* 從函數返回;
2. 如果page所處于的Extent當前在該segment的FSEG_FULL鏈表上,則轉移到`FSEG_NOT_FULL`鏈表;
3. 設置Page在xdes entry的bitmap對應的XDES_FREE_BIT為true;
4. 如果此時該Extent上的page全部被釋放了,將其從`FSEG_NOT_FULL`鏈表上移除,并加入到表空間的`FSP_FREE`鏈表上(而非Segment的`FSEG_FREE`鏈表)。
釋放Segment
當我們刪除索引或者表時,需要刪除btree(`btr_free_if_exists`),先刪除除了root節點外的其他部分(`btr_free_but_not_root`),再刪除root節點(`btr_free_root`)
由于數據操作都需要記錄redo,為了避免產生非常大的redo log,leaf segment通過反復調用函數`fseg_free_step`來釋放其占用的數據頁:
1. 首先找到leaf segment對應的Inode entry(`fseg_inode_try_get`);
2. 然后依次查找inode entry中的`FSEG_FULL`、或者`FSEG_NOT_FULL`、或者`FSEG_FREE`鏈表,找到一個Extent,注意著里的鏈表元組所指向的位置實際上是描述該Extent的Xdes Entry所在的位置。因此可以快速定位到對應的Xdes Page及Page內偏移量(`xdes_lst_get_descriptor`);
3. 現在我們可以將這個Extent安全的釋放了(`fseg_free_extent`,見后文);
4. 當反復調用`fseg_free_step`將所有的Extent都釋放后,segment還會最多占用32個碎片頁,也需要依次釋放掉(`fseg_free_page_low`)
5. 最后,當該inode所占用的page全部釋放時,釋放inode entry:
* 如果該inode所在的inode page中當前被用滿,則由于我們即將釋放一個slot,需要從`FSP_SEG_INODES_FULL`轉移到`FSP_SEG_INODES_FREE`(更新第一個page);
* 將該inode entry的SEG_ID清除為0,表示未使用;
* 如果該inode page上全部inode entry都釋放了,就從`FSP_SEG_INODES_FREE`移除,并刪除該page。
non-leaf segment的回收和leaf segment的回收基本類似,但要注意btree的根節點存儲在該segment的frag arrary的第一個元組中,該Page暫時不可以釋放(`fseg_free_step_not_header`)
btree的root page在完成上述步驟后再釋放,此時才能徹底釋放non-leaf segment
## 索引頁
ibd文件中真正構建起用戶數據的結構是BTREE,在你創建一個表時,已經基于顯式或隱式定義的主鍵構建了一個btree,其葉子節點上記錄了行的全部列數據(加上事務id列及回滾段指針列);如果你在表上創建了二級索引,其葉子節點存儲了鍵值加上聚集索引鍵值。本小節我們探討下組成索引的物理存儲頁結構,這里默認討論的是非壓縮頁,我們在下一小節介紹壓縮頁的內容。
每個btree使用兩個Segment來管理數據頁,一個管理葉子節點,一個管理非葉子節點,每個segment在inode page中存在一個記錄項,在btree的root page中記錄了兩個segment信息。
當我們需要打開一張表時,需要從ibdata的數據詞典表中load元數據信息,其中SYS_INDEXES系統表中記錄了表,索引,及索引根頁對應的page no(`DICT_FLD__SYS_INDEXES__PAGE_NO`),進而找到btree根page,就可以對整個用戶數據btree進行操作。
索引最基本的頁類型為`FIL_PAGE_INDEX`。可以劃分為下面幾個部分。
Page Header
首先不管任何類型的數據頁都有38個字節來描述頭信息(`FIL_PAGE_DATA`, or?`PAGE_HEADER`),包含如下信息:
| Macro | bytes | Desc |
| --- | --- | --- |
| FIL_PAGE_SPACE_OR_CHKSUM | 4 | 在MySQL4.0之前存儲space id,之后的版本用于存儲checksum |
| FIL_PAGE_OFFSET | 4 | 當前頁的page no |
| FIL_PAGE_PREV | 4 | 通常用于維護btree同一level的雙向鏈表,指向鏈表的前一個page,沒有的話則值為FIL_NULL |
| FIL_PAGE_NEXT | 4 | 和FIL_PAGE_PREV類似,記錄鏈表的下一個Page的Page No |
| FIL_PAGE_LSN | 8 | 最近一次修改該page的LSN |
| FIL_PAGE_TYPE | 2 | Page類型 |
| FIL_PAGE_FILE_FLUSH_LSN | 8 | 只用于系統表空間的第一個Page,記錄在正常shutdown時安全checkpoint到的點,對于用戶表空間,這個字段通常是空閑的,但在5.7里,FIL_PAGE_COMPRESSED類型的數據頁則另有用途。下一小節單獨介紹 |
| FIL_PAGE_SPACE_ID | 4 | 存儲page所在的space id |
Index Header
緊隨`FIL_PAGE_DATA`之后的是索引信息,這部分信息是索引頁獨有的。
| Macro | bytes | Desc |
| --- | --- | --- |
| PAGE_N_DIR_SLOTS | 2 | Page directory中的slot個數 (見下文關于Page directory的描述) |
| PAGE_HEAP_TOP | 2 | 指向當前Page內已使用的空間的末尾便宜位置,即free space的開始位置 |
| PAGE_N_HEAP | 2 | Page內所有記錄個數,包含用戶記錄,系統記錄以及標記刪除的記錄,同時當第一個bit設置為1時,表示這個page內是以Compact格式存儲的 |
| PAGE_FREE | 2 | 指向標記刪除的記錄鏈表的第一個記錄 |
| PAGE_GARBAGE | 2 | 被刪除的記錄鏈表上占用的總的字節數,屬于可回收的垃圾碎片空間 |
| PAGE_LAST_INSERT | 2 | 指向最近一次插入的記錄偏移量,主要用于優化順序插入操作 |
| PAGE_DIRECTION | 2 | 用于指示當前記錄的插入順序以及是否正在進行順序插入,每次插入時,PAGE_LAST_INSERT會和當前記錄進行比較,以確認插入方向,據此進行插入優化 |
| PAGE_N_DIRECTION | 2 | 當前以相同方向的順序插入記錄個數 |
| PAGE_N_RECS | 2 | Page上有效的未被標記刪除的用戶記錄個數 |
| PAGE_MAX_TRX_ID | 8 | 最近一次修改該page記錄的事務ID,主要用于輔助判斷二級索引記錄的可見性。 |
| PAGE_LEVEL | 2 | 該Page所在的btree level,根節點的level最大,葉子節點的level為0 |
| PAGE_INDEX_ID | 8 | 該Page歸屬的索引ID |
Segment Info
隨后20個字節描述段信息,僅在Btree的root Page中被設置,其他Page都是未使用的。
| Macro | bytes | Desc |
| --- | --- | --- |
| PAGE_BTR_SEG_LEAF | 10(FSEG_HEADER_SIZE) | leaf segment在inode page中的位置 |
| PAGE_BTR_SEG_TOP | 10(FSEG_HEADER_SIZE) | non-leaf segment在inode page中的位置 |
10個字節的inode信息包括:
| Macro | bytes | Desc |
| --- | --- | --- |
| FSEG_HDR_SPACE | 4 | 描述該segment的inode page所在的space id (目前的實現來看,感覺有點多余…) |
| FSEG_HDR_PAGE_NO | 4 | 描述該segment的inode page的page no |
| FSEG_HDR_OFFSET | 2 | inode page內的頁內偏移量 |
通過上述信息,我們可以找到對應segment在inode page中的描述項,進而可以操作整個segment。
系統記錄
之后是兩個系統記錄,分別用于描述該page上的極小值和極大值,這里存在兩種存儲方式,分別對應舊的InnoDB文件系統,及新的文件系統(compact page)
| Macro | bytes | Desc |
| --- | --- | --- |
| REC_N_OLD_EXTRA_BYTES + 1 | 7 | 固定值,見infimum_supremum_redundant的注釋 |
| PAGE_OLD_INFIMUM | 8 | “infimum\0” |
| REC_N_OLD_EXTRA_BYTES + 1 | 7 | 固定值,見infimum_supremum_redundant的注釋 |
| PAGE_OLD_SUPREMUM | 9 | “supremum\0” |
Compact的系統記錄存儲方式為:
| Macro | bytes | Desc |
| --- | --- | --- |
| REC_N_NEW_EXTRA_BYTES | 5 | 固定值,見infimum_supremum_compact的注釋 |
| PAGE_NEW_INFIMUM | 8 | “infimum\0” |
| REC_N_NEW_EXTRA_BYTES | 5 | 固定值,見infimum_supremum_compact的注釋 |
| PAGE_NEW_SUPREMUM | 8 | “supremum”,這里不帶字符0 |
兩種格式的主要差異在于不同行存儲模式下,單個記錄的描述信息不同。在實際創建page時,系統記錄的值已經初始化好了,對于老的格式(REDUNDANT),對應代碼里的`infimum_supremum_redundant`,對于新的格式(compact),對應`infimum_supremum_compact`。infimum記錄的固定heap no為0,supremum記錄的固定Heap no 為1。page上最小的用戶記錄前節點總是指向infimum,page上最大的記錄后節點總是指向supremum記錄。
具體參考索引頁創建函數:`page_create_low`
用戶記錄
在系統記錄之后就是真正的用戶記錄了,heap no 從2(`PAGE_HEAP_NO_USER_LOW`)開始算起。注意Heap no僅代表物理存儲順序,不代表鍵值順序。
根據不同的類型,用戶記錄可以是非葉子節點的Node指針信息,也可以是只包含有效數據的葉子節點記錄。而不同的行格式存儲的行記錄也不同,例如在早期版本中使用的redundant格式會被現在的compact格式使用更多的字節數來描述記錄,例如描述記錄的一些列信息,在使用compact格式時,可以改為直接從數據詞典獲取。因為redundant屬于漸漸被拋棄的格式,本文的討論中我們默認使用Compact格式。在文件rem/rem0rec.cc的頭部注釋描述了記錄的物理結構。
每個記錄都存在rec header,描述如下(參閱文件include/rem0rec.ic)
| bytes | Desc |
| --- | --- |
| 變長列長度數組 | 如果列的最大長度為255字節,使用1byte;否則,0xxxxxxx (one byte, length=0..127), or 1exxxxxxxxxxxxxx (two bytes, length=128..16383, extern storage flag) |
| SQL-NULL flag | 標示值為NULL的列的bitmap,每個位標示一個列,bitmap的長度取決于索引上可為NULL的列的個數(dict_index_t::n_nullable),這兩個數組的解析可以參閱函數`rec_init_offsets` |
| 下面5個字節(REC_N_NEW_EXTRA_BYTES)描述記錄的額外信息 | …. |
| REC_NEW_INFO_BITS (4 bits) | 目前只使用了兩個bit,一個用于表示該記錄是否被標記刪除(`REC_INFO_DELETED_FLAG`),另一個bit(REC_INFO_MIN_REC_FLAG)如果被設置,表示這個記錄是當前level最左邊的page的第一個用戶記錄 |
| REC_NEW_N_OWNED (4 bits) | 當該值為非0時,表示當前記錄占用page directory里一個slot,并和前一個slot之間存在這么多個記錄 |
| REC_NEW_HEAP_NO (13 bits) | 該記錄的heap no |
| REC_NEW_STATUS (3 bits) | 記錄的類型,包括四種:`REC_STATUS_ORDINARY`(葉子節點記錄),?`REC_STATUS_NODE_PTR`(非葉子節點記錄),`REC_STATUS_INFIMUM`(infimum系統記錄)以及`REC_STATUS_SUPREMUM`(supremum系統記錄) |
| REC_NEXT (2bytes) | 指向按照鍵值排序的page內下一條記錄數據起點,這里存儲的是和當前記錄的相對位置偏移量(函數`rec_set_next_offs_new`) |
在記錄頭信息之后的數據視具體情況有所不同:
* 對于聚集索引記錄,數據包含了事務id,回滾段指針;
* 對于二級索引記錄,數據包含了二級索引鍵值以及聚集索引鍵值。如果二級索引鍵和聚集索引有重合,則只保留一份重合的,例如pk (col1, col2),sec key(col2, col3),在二級索引記錄中就只包含(col2, col3, col1);
* 對于非葉子節點頁的記錄,聚集索引上包含了其子節點的最小記錄鍵值及對應的page no;二級索引上有所不同,除了二級索引鍵值外,還包含了聚集索引鍵值,再加上page no三部分構成。
Free space
這里指的是一塊完整的未被使用的空間,范圍在頁內最后一個用戶記錄和Page directory之間。通常如果空間足夠時,直接從這里分配記錄空間。當判定空閑空間不足時,會做一次Page內的重整理,以對碎片空間進行合并。
Page directory
為了加快頁內的數據查找,會按照記錄的順序,每隔4~8個數量(`PAGE_DIR_SLOT_MIN_N_OWNED`?~?`PAGE_DIR_SLOT_MAX_N_OWNED`)的用戶記錄,就分配一個slot (每個slot占用2個字節,`PAGE_DIR_SLOT_SIZE`),存儲記錄的頁內偏移量,可以理解為在頁內構建的一個很小的索引(sparse index)來輔助二分查找。
Page Directory的slot分配是從Page末尾(倒數第八個字節開始)開始逆序分配的。在查詢記錄時。先根據page directory 確定記錄所在的范圍,然后在據此進行線性查詢。
增加slot的函數參閱?`page_dir_add_slot`
頁內記錄二分查找的函數參閱?`page_cur_search_with_match_bytes`
FIL Trailer
在每個文件頁的末尾保留了8個字節(`FIL_PAGE_DATA_END`?or?`FIL_PAGE_END_LSN_OLD_CHKSUM`),其中4個字節用于存儲page checksum,這個值需要和page頭部記錄的checksum相匹配,否則認為page損壞(`buf_page_is_corrupted`)
## 壓縮索引頁
InnoDB當前存在兩種形式的壓縮頁,一種是Transparent Page Compression,還有一種是傳統的壓縮方式,下文分別進行闡述。
### Transparent Page Compression
這是MySQL5.7新加的一種數據壓縮方式,其原理是利用內核Punch hole特性,對于一個16kb的數據頁,在寫文件之前,除了Page頭之外,其他部分進行壓縮,壓縮后留白的地方使用punch hole進行 “打洞”,在磁盤上表現為不占用空間 (但會產生大量的磁盤碎片)。 這種方式相比傳統的壓縮方式具有更好的壓縮比,實現邏輯也更加簡單。
對于這種壓縮方式引入了新的類型`FIL_PAGE_COMPRESSED`,在存儲格式上略有不同,主要表現在從`FIL_PAGE_FILE_FLUSH_LSN`開始的8個字節被用作記錄壓縮信息:
| Macro | bytes | Desc |
| --- | --- | --- |
| FIL_PAGE_VERSION | 1 | 版本,目前為1 |
| FIL_PAGE_ALGORITHM_V1 | 1 | 使用的壓縮算法 |
| FIL_PAGE_ORIGINAL_TYPE_V1 | 2 | 壓縮前的Page類型,解壓后需要恢復回去 |
| FIL_PAGE_ORIGINAL_SIZE_V1 | 2 | 未壓縮時去除FIL_PAGE_DATA后的數據長度 |
| FIL_PAGE_COMPRESS_SIZE_V1 | 2 | 壓縮后的長度 |
打洞后的page其實際存儲空間需要是磁盤的block size的整數倍。
這里我們不展開闡述,具體參閱我之前寫的這篇文章:[MySQL · 社區動態 · InnoDB Page Compression](http://mysql.taobao.org/monthly/2015/08/01/)
### 傳統壓縮存儲格式
當你創建或修改表,指定`row_format=compressed key_block_size=1|2|4|8`?時,創建的ibd文件將以對應的block size進行劃分。例如`key_block_size`設置為4時,對應block size為4kb。
壓縮頁的格式可以描述如下表所示:
| Macro | Desc |
| --- | --- |
| FIL_PAGE_HEADER | 頁面頭數據,不做壓縮 |
| Index Field Information | 索引的列信息,參閱函數`page_zip_fields_encode`及`page_zip_fields_decode`,在崩潰恢復時可以據此恢復出索引信息 |
| Compressed Data | 壓縮數據,按照heap no排序進入壓縮流,壓縮數據不包含系統列(trx_id, roll_ptr)或外部存儲頁指針 |
| Modification Log(mlog) | 壓縮頁修改日志 |
| Free Space | 空閑空間 |
| External_Ptr (optional) | 存在外部存儲頁的列記錄指針數組,只存在聚集索引葉子節點,每個數組元素占20個字節(`BTR_EXTERN_FIELD_REF_SIZE`),參閱函數`page_zip_compress_clust_ext` |
| Trx_id, Roll_Ptr(optional) | 只存在于聚集索引葉子節點,數組元素和其heap no一一對應 |
| Node_Ptr | 只存在于索引非葉子節點,存儲節點指針數組,每個元素占用4字節(REC_NODE_PTR_SIZE) |
| Dense Page Directory | 分兩部分,第一部分是有效記錄,記錄其在解壓頁中的偏移位置,n_owned和delete標記信息,按照鍵值順序;第二部分是空閑記錄;每個slot占兩個字節。 |
在內存中通常存在壓縮頁和解壓頁兩份數據。當對數據進行修改時,通常先修改解壓頁,再將DML操作以一種特殊日志的格式記入壓縮頁的mlog中。以減少被修改過程中重壓縮的次數。主要包含這幾種操作:
* Insert: 向mlog中寫入完整記錄
* Update:
* Delete-insert update,將舊記錄的dense slot標記為刪除,再寫入完整新記錄
* In-place update,直接寫入新更新的記錄
* Delete: 標記對應的dense slot為刪除
頁壓縮參閱函數?`page_zip_compress`
頁解壓參閱函數?`page_zip_decompress`
## 系統數據頁
這里我們將所有非獨立的數據頁統稱為系統數據頁,主要存儲在ibdata中,如下圖所示:

InnoDB 系統數據頁
ibdata的三個page和普通的用戶表空間一樣,都是用于維護和管理文件頁。其他Page我們下面一一進行介紹。
FSP_IBUF_HEADER_PAGE_NO
Ibdata的第4個page是Change Buffer的header page,類型為`FIL_PAGE_TYPE_SYS`,主要用于對ibuf btree的Page管理。
FSP_IBUF_TREE_ROOT_PAGE_NO
用于存儲change buffer的根page,change buffer目前存儲于Ibdata中,其本質上也是一顆btree,root頁為固定page,也就是Ibdata的第5個page。
IBUF HEADER Page 和Root Page聯合起來對ibuf的數據頁進行管理。
首先Ibuf btree自己維護了一個空閑Page鏈表,鏈表頭記錄在根節點中,偏移量在`PAGE_BTR_IBUF_FREE_LIST`處,實際上利用的是普通索引根節點的`PAGE_BTR_SEG_LEAF`字段。Free List上的Page類型標示為`FIL_PAGE_IBUF_FREE_LIST`
每個Ibuf page重用了`PAGE_BTR_SEG_LEAF`字段,以維護IBUF FREE LIST的前后文件頁節點(`PAGE_BTR_IBUF_FREE_LIST_NODE`)。
由于root page中的segment字段已經被重用,因此額外的開辟了一個Page,也就是Ibdata的第4個page來進行段管理。在其中記錄了ibuf btree的segment header,指向屬于ibuf btree的inode entry。
關于ibuf btree的構建參閱函數?`btr_create`
FSP_TRX_SYS_PAGE_NO/FSP_FIRST_RSEG_PAGE_NO
ibdata的第6個page,記錄了InnoDB重要的事務系統信息,主要包括:
| Macro | bytes | Desc |
| --- | --- | --- |
| TRX_SYS | 38 | 每個數據頁都會保留的文件頭字段 |
| TRX_SYS_TRX_ID_STORE | 8 | 持久化的最大事務ID,這個值不是實時寫入的,而是256次遞增寫一次 |
| TRX_SYS_FSEG_HEADER | 10 | 指向用來管理事務系統的segment所在的位置 |
| TRX_SYS_RSEGS | 128 * 8 | 用于存儲128個回滾段位置,包括space id及page no。每個回滾段包含一個文件segment(`trx_rseg_header_create`) |
| …… | 以下是Page內UNIV_PAGE_SIZE - 1000的偏移位置 | ? |
| TRX_SYS_MYSQL_LOG_MAGIC_N_FLD | 4 | Magic Num ,值為873422344 |
| TRX_SYS_MYSQL_LOG_OFFSET_HIGH | 4 | 事務提交時會將其binlog位點更新到該page中,這里記錄了在binlog文件中偏移量的高位的4字節 |
| TRX_SYS_MYSQL_LOG_OFFSET_LOW | 4 | 同上,記錄偏移量的低4位字節 |
| TRX_SYS_MYSQL_LOG_NAME | 4 | 記錄所在的binlog文件名 |
| …… | 以下是Page內UNIV_PAGE_SIZE - 200 的偏移位置 | ? |
| TRX_SYS_DOUBLEWRITE_FSEG | 10 | 包含double write buffer的fseg header |
| TRX_SYS_DOUBLEWRITE_MAGIC | 4 | Magic Num |
| TRX_SYS_DOUBLEWRITE_BLOCK1 | 4 | double write buffer的第一個block(占用一個Extent)在ibdata中的開始位置,連續64個page |
| TRX_SYS_DOUBLEWRITE_BLOCK2 | 4 | 第二個dblwr block的起始位置 |
| TRX_SYS_DOUBLEWRITE_REPEAT | 12 | 重復記錄上述三個字段,即MAGIC NUM, block1, block2,防止發生部分寫時可以恢復 |
| TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED | 4 | 用于兼容老版本,當該字段的值不為TRX_SYS_DOUBLEWRITE_SPACE_ID_STORED_N時,需要重置dblwr中的數據 |
在5.7版本中,回滾段既可以在ibdata中,也可以在獨立undo表空間,或者ibtmp臨時表空間中,一個可能的分布如下圖所示(摘自我之前的[這篇文章](http://mysql.taobao.org/monthly/2015/04/01/))。

InnoDB Undo 回滾段結構
由于是在系統剛啟動時初始化事務系統,因此第0號回滾段頭頁總是在ibdata的第7個page中。
事務系統創建參閱函數?`trx_sysf_create`
InnoDB最多可以創建128個回滾段,每個回滾段需要單獨的Page來維護其擁有的undo slot,Page類型為`FIL_PAGE_TYPE_SYS`。描述如下:
| Macro | bytes | Desc |
| --- | --- | --- |
| TRX_RSEG | 38 | 保留的Page頭 |
| TRX_RSEG_MAX_SIZE | 4 | 回滾段允許使用的最大Page數,當前值為ULINT_MAX |
| TRX_RSEG_HISTORY_SIZE | 4 | 在history list上的undo page數,這些page需要由purge線程來進行清理和回收 |
| TRX_RSEG_HISTORY | FLST_BASE_NODE_SIZE(16) | history list的base node |
| TRX_RSEG_FSEG_HEADER | (FSEG_HEADER_SIZE)10 | 指向當前管理當前回滾段的inode entry |
| TRX_RSEG_UNDO_SLOTS | 1024 * 4 | undo slot數組,共1024個slot,值為FIL_NULL表示未被占用,否則記錄占用該slot的第一個undo page |
回滾段頭頁的創建參閱函數?`trx_rseg_header_create`
實際存儲undo記錄的Page類型為`FIL_PAGE_UNDO_LOG`,undo header結構如下
| Macro | bytes | Desc |
| --- | --- | --- |
| TRX_UNDO_PAGE_HDR | 38 | Page 頭 |
| TRX_UNDO_PAGE_TYPE | 2 | 記錄Undo類型,是TRX_UNDO_INSERT還是TRX_UNDO_UPDATE |
| TRX_UNDO_PAGE_START | 2 | 事務所寫入的最近的一個undo log在page中的偏移位置 |
| TRX_UNDO_PAGE_FREE | 2 | 指向當前undo page中的可用的空閑空間起始偏移量 |
| TRX_UNDO_PAGE_NODE | 12 | 鏈表節點,提交后的事務,其擁有的undo頁會加到history list上 |
undo頁內結構及其與回滾段頭頁的關系參閱下圖:

InnoDB Undo 頁內結構
關于具體的Undo log如何存儲,本文不展開描述,可閱讀我之前的這篇文章:[MySQL · 引擎特性 · InnoDB undo log 漫游](http://mysql.taobao.org/monthly/2015/04/01/)
FSP_DICT_HDR_PAGE_NO
ibdata的第8個page,用來存儲數據詞典表的信息 (只有拿到數據詞典表,才能根據其中存儲的表信息,進一步找到其對應的表空間,以及表的聚集索引所在的page no)
Dict_Hdr Page的結構如下表所示:
| Macro | bytes | Desc |
| --- | --- | --- |
| DICT_HDR | 38 | Page頭 |
| DICT_HDR_ROW_ID | 8 | 最近被賦值的row id,遞增,用于給未定義主鍵的表,作為其隱藏的主鍵鍵值來構建btree |
| DICT_HDR_TABLE_ID | 8 | 當前系統分配的最大事務ID,每創建一個新表,都賦予一個唯一的table id,然后遞增 |
| DICT_HDR_INDEX_ID | 8 | 用于分配索引ID |
| DICT_HDR_MAX_SPACE_ID | 4 | 用于分配space id |
| DICT_HDR_MIX_ID_LOW | 4 | ? |
| DICT_HDR_TABLES | 4 | SYS_TABLES系統表的聚集索引root page |
| DICT_HDR_TABLE_IDS | 4 | SYS_TABLE_IDS索引的root page |
| DICT_HDR_COLUMNS | 4 | SYS_COLUMNS系統表的聚集索引root page |
| DICT_HDR_INDEXES | 4 | SYS_INDEXES系統表的聚集索引root page |
| DICT_HDR_FIELDS | 4 | SYS_FIELDS系統表的聚集索引root page |
dict_hdr頁的創建參閱函數?`dict_hdr_create`
double write buffer
InnoDB使用double write buffer來防止數據頁的部分寫問題,在寫一個數據頁之前,總是先寫double write buffer,再寫數據文件。當崩潰恢復時,如果數據文件中page損壞,會嘗試從dblwr中恢復。
double write buffer存儲在ibdata中,你可以從事務系統頁(ibdata的第6個page)獲取dblwr所在的位置。總共128個page,劃分為兩個block。由于dblwr在安裝實例時已經初始化好了,這兩個block在Ibdata中具有固定的位置,Page64 ~127 劃屬第一個block,Page 128 ~191劃屬第二個block。
在這128個page中,前120個page用于batch flush時的臟頁回寫,另外8個page用于SINGLE PAGE FLUSH時的臟頁回寫。
## 外部存儲頁
對于大字段,在滿足一定條件時InnoDB使用外部頁進行存儲。外部存儲頁有三種類型:
1. `FIL_PAGE_TYPE_BLOB`:表示非壓縮的外部存儲頁,結構如下圖所示:

2. `FIL_PAGE_TYPE_ZBLOB`:壓縮的外部存儲頁,如果存在多個blob page,則表示第一個
`FIL_PAGE_TYPE_ZBLOB2`:如果存在多個壓縮的blob page,則表示blob鏈隨后的page;
結構如下圖所示:

而在記錄內只存儲了20個字節的指針以指向外部存儲頁,指針描述如下:
| Macro | bytes | Desc |
| --- | --- | --- |
| BTR_EXTERN_SPACE_ID | 4 | 外部存儲頁所在的space id |
| BTR_EXTERN_PAGE_NO | 4 | 第一個外部頁的Page no |
| BTR_EXTERN_OFFSET | 4 | 對于壓縮頁,為12,該偏移量存儲了指向下一個外部頁的的page no;對于非壓縮頁,值為38,指向blob header,如上圖所示 |
外部頁的寫入參閱函數?`btr_store_big_rec_extern_fields`
## MySQL5.7新數據頁:加密頁及R-TREE頁
MySQL 5.7版本引入了新的數據頁以支持表空間加密及對空間數據類型建立R-TREE索引。本文對這種數據頁不做深入討論,僅僅簡單描述下,后面我們會單獨開兩篇文章分別進行介紹。
數據加密頁
從MySQL5.7.11開始InnoDB支持對單表進行加密,因此引入了新的Page類型來支持這一特性,主要加了三種Page類型:
* `FIL_PAGE_ENCRYPTED`:加密的普通數據頁
* `FIL_PAGE_COMPRESSED_AND_ENCRYPTED`:數據頁為壓縮頁(transparent page compression) 并且被加密(先壓縮,再加密)
* `FIL_PAGE_ENCRYPTED_RTREE`:GIS索引R-TREE的數據頁并被加密
對于加密頁,除了數據部分被替換成加密數據外,其他部分和大多數表都是一樣的結構。
加解密的邏輯和Transparent Compression類似,在寫入文件前加密(`os_file_encrypt_page --> Encryption::encrypt`),在讀出文件時解密數據(`os_file_io_complete --> Encryption::decrypt`)
秘鑰信息存儲在ibd文件的第一個page中(`fsp_header_init --> fsp_header_fill_encryption_info`),當執行SQL?`ALTER INSTANCE ROTATE INNODB MASTER KEY`時,會更新每個ibd存儲的秘鑰信息(`fsp_header_rotate_encryption`)
默認安裝時,一個新的插件`keyring_file`被安裝并且默認Active,在安裝目錄下,會產生一個新的文件來存儲秘鑰,位置在$MYSQL_INSTALL_DIR/keyring/keyring,你可以通過參數[keyring_file_data](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_keyring_file_data)來指定秘鑰的存放位置和文件命名。 當你安裝多實例時,需要為不同的實例指定keyring文件。
開啟表加密的語法很簡單,在CREATE TABLE或ALTER TABLE時指定選項ENCRYPTION=‘Y’來開啟,或者ENCRYPTION=‘N’來關閉加密。
關于InnoDB表空間加密特性,參閱該[commit](https://github.com/mysql/mysql-server/commit/9340eb1146fedc538cc54e96a45f95a58b345fbf)及[官方文檔](http://dev.mysql.com/doc/refman/5.7/en/innodb-tablespace-encryption.html)。
R-TREE索引頁
在MySQL 5.7中引入了新的索引類型R-TREE來描述空間數據類型的多維數據結構,這類索引的數據頁類型為`FIL_PAGE_RTREE`。
R-TREE的相關設計參閱官方[WL#6968](http://dev.mysql.com/worklog/task/?id=6968),?[WL#6609](http://dev.mysql.com/worklog/task/?id=6609),?[WL#6745](http://dev.mysql.com/worklog/task/?id=6745)
## 臨時表空間ibtmp
MySQL5.7引入了臨時表專用的表空間,默認命名為ibtmp1,創建的非壓縮臨時表都存儲在該表空間中。系統重啟后,ibtmp1會被重新初始化到默認12MB。你可以通過設置參數[innodb_temp_data_file_path](http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_temp_data_file_path)來修改ibtmp1的默認初始大小,以及是否允許autoExtent。默認值為 “ibtmp1:12M:autoExtent”。
除了用戶定義的非壓縮臨時表外,第1~32個臨時表專用的回滾段也存放在該文件中(0號回滾段總是存放在ibdata中)(`trx_sys_create_noredo_rsegs`),
## 日志文件ib_logfile
關于日志文件的格式,網上已經有很多的討論,在之前的[系列文章](http://mysql.taobao.org/monthly/2015/05/01/)中我也有專門介紹過,本小節主要介紹下MySQL5.7新的修改。
首先是checksum算法的改變,當前版本的MySQL5.7可以通過參數`innodb_log_checksums`來開啟或關閉redo checksum,但目前唯一支持的checksum算法是CRC32。而在之前老版本中只支持效率較低的InnoDB本身的checksum算法。
第二個改變是為Redo log引入了版本信息([WL#8845](http://dev.mysql.com/worklog/task/?id=8845)),存儲在ib_logfile的頭部,從文件頭開始,描述如下
| Macro | bytes | Desc |
| --- | --- | --- |
| LOG_HEADER_FORMAT | 4 | 當前值為1(LOG_HEADER_FORMAT_CURRENT),在老版本中這里的值總是為0 |
| LOG_HEADER_PAD1 | 4 | 新版本未使用 |
| LOG_HEADER_START_LSN | 8 | 當前iblogfile的開始LSN |
| LOG_HEADER_CREATOR | 32 | 記錄版本信息,和MySQL版本相關,例如在5.7.11中,這里存儲的是”MySQL 5.7.11”(LOG_HEADER_CREATOR_CURRENT) |
每次切換到下一個iblogfile時,都會更新該文件頭信息(`log_group_file_header_flush`)
新的版本支持兼容老版本(`recv_find_max_checkpoint_0`),但升級到新版本后,就無法在異常狀態下in-place降級到舊版本了(除非做一次clean的shutdown,并清理掉iblogfile)。
具體實現參閱該[commit](https://github.com/mysql/mysql-server/commit/af0acedd885eb7103e319f79d25fda7386ef1506)。
- 數據庫內核月報目錄
- 數據庫內核月報 - 2016/09
- MySQL · 社區貢獻 · AliSQL那些事兒
- PetaData · 架構體系 · PetaData第二代低成本存儲體系
- MySQL · 社區動態 · MariaDB 10.2 前瞻
- MySQL · 特性分析 · 執行計劃緩存設計與實現
- PgSQL · 最佳實踐 · pg_rman源碼淺析與使用
- MySQL · 捉蟲狀態 · bug分析兩例
- PgSQL · 源碼分析 · PG優化器淺析
- MongoDB · 特性分析· Sharding原理與應用
- PgSQL · 源碼分析 · PG中的無鎖算法和原子操作應用一則
- SQLServer · 最佳實踐 · TEMPDB的設計
- 數據庫內核月報 - 2016/08
- MySQL · 特性分析 ·MySQL 5.7新特性系列四
- PgSQL · PostgreSQL 邏輯流復制技術的秘密
- MySQL · 特性分析 · MyRocks簡介
- GPDB · 特性分析· Greenplum 備份架構
- SQLServer · 最佳實踐 · RDS for SQLServer 2012權限限制提升與改善
- TokuDB · 引擎特性 · REPLACE 語句優化
- MySQL · 專家投稿 · InnoDB物理行中null值的存儲的推斷與驗證
- PgSQL · 實戰經驗 · 旋轉門壓縮算法在PostgreSQL中的實現
- MySQL · 源碼分析 · Query Cache并發處理
- PgSQL · 源碼分析· pg_dump分析
- 數據庫內核月報 - 2016/07
- MySQL · 特性分析 ·MySQL 5.7新特性系列三
- MySQL · 特性分析 · 5.7 代價模型淺析
- PgSQL · 實戰經驗 · 分組TOP性能提升44倍
- MySQL · 源碼分析 · 網絡通信模塊淺析
- MongoDB · 特性分析 · 索引原理
- SQLServer · 特性分析 · XML與JSON應用比較
- MySQL · 最佳實戰 · 審計日志實用案例分析
- MySQL · 性能優化 · 條件下推到物化表
- MySQL · 源碼分析 · Query Cache內部剖析
- MySQL · 捉蟲動態 · 備庫1206錯誤問題說明
- 數據庫內核月報 - 2016/06
- MySQL · 特性分析 · innodb 鎖分裂繼承與遷移
- MySQL · 特性分析 ·MySQL 5.7新特性系列二
- PgSQL · 實戰經驗 · 如何預測Freeze IO風暴
- GPDB · 特性分析· Filespace和Tablespace
- MariaDB · 新特性 · 窗口函數
- MySQL · TokuDB · checkpoint過程
- MySQL · 特性分析 · 內部臨時表
- MySQL · 最佳實踐 · 空間優化
- SQLServer · 最佳實踐 · 數據庫實現大容量插入的幾種方式
- 數據庫內核月報 - 2016/05
- MySQL · 引擎特性 · 基于InnoDB的物理復制實現
- MySQL · 特性分析 · MySQL 5.7新特性系列一
- PostgreSQL · 特性分析 · 邏輯結構和權限體系
- MySQL · 特性分析 · innodb buffer pool相關特性
- PG&GP · 特性分析 · 外部數據導入接口實現分析
- SQLServer · 最佳實踐 · 透明數據加密在SQLServer的應用
- MySQL · TokuDB · 日志子系統和崩潰恢復過程
- MongoDB · 特性分析 · Sharded cluster架構原理
- PostgreSQL · 特性分析 · 統計信息計算方法
- MySQL · 捉蟲動態 · left-join多表導致crash
- 數據庫內核月報 - 2016/04
- MySQL · 參數故事 · innodb_additional_mem_pool_size
- GPDB · 特性分析 · Segment事務一致性與異常處理
- GPDB · 特性分析 · Segment 修復指南
- MySQL · 捉蟲動態 · 并行復制外鍵約束問題二
- PgSQL · 性能優化 · 如何瀟灑的處理每天上百TB的數據增量
- Memcached · 最佳實踐 · 熱點 Key 問題解決方案
- MongoDB · 最佳實踐 · 短連接Auth性能優化
- MySQL · 最佳實踐 · RDS 只讀實例延遲分析
- MySQL · TokuDB · TokuDB索引結構--Fractal Tree
- MySQL · TokuDB · Savepoint漫談
- 數據庫內核月報 - 2016/03
- MySQL · TokuDB · 事務子系統和 MVCC 實現
- MongoDB · 特性分析 · MMAPv1 存儲引擎原理
- PgSQL · 源碼分析 · 優化器邏輯推理
- SQLServer · BUG分析 · Agent 鏈接泄露分析
- Redis · 特性分析 · AOF Rewrite 分析
- MySQL · BUG分析 · Rename table 死鎖分析
- MySQL · 物理備份 · Percona XtraBackup 備份原理
- GPDB · 特性分析· GreenPlum FTS 機制
- MySQL · 答疑解惑 · 備庫Seconds_Behind_Master計算
- MySQL · 答疑解惑 · MySQL 鎖問題最佳實踐
- 數據庫內核月報 - 2016/02
- MySQL · 引擎特性 · InnoDB 文件系統之文件物理結構
- MySQL · 引擎特性 · InnoDB 文件系統之IO系統和內存管理
- MySQL · 特性分析 · InnoDB transaction history
- PgSQL · 會議見聞 · PgConf.Russia 2016 大會總結
- PgSQL · 答疑解惑 · PostgreSQL 9.6 并行查詢實現分析
- MySQL · TokuDB · TokuDB之黑科技工具
- PgSQL · 性能優化 · PostgreSQL TPC-C極限優化玩法
- MariaDB · 版本特性 · MariaDB 的 GTID 介紹
- MySQL · 特性分析 · 線程池
- MySQL · 答疑解惑 · mysqldump tips 兩則
- 數據庫內核月報 - 2016/01
- MySQL · 引擎特性 · InnoDB 事務鎖系統簡介
- GPDB · 特性分析· GreenPlum Primary/Mirror 同步機制
- MySQL · 專家投稿 · MySQL5.7 的 JSON 實現
- MySQL · 特性分析 · 優化器 MRR & BKA
- MySQL · 答疑解惑 · 物理備份死鎖分析
- MySQL · TokuDB · Cachetable 的工作線程和線程池
- MySQL · 特性分析 · drop table的優化
- MySQL · 答疑解惑 · GTID不一致分析
- PgSQL · 特性分析 · Plan Hint
- MariaDB · 社區動態 · MariaDB on Power8 (下)
- 數據庫內核月報 - 2015/12
- MySQL · 引擎特性 · InnoDB 事務子系統介紹
- PgSQL · 特性介紹 · 全文搜索介紹
- MongoDB · 捉蟲動態 · Kill Hang問題排查記錄
- MySQL · 參數優化 ·RDS MySQL參數調優最佳實踐
- PgSQL · 特性分析 · 備庫激活過程分析
- MySQL · TokuDB · 讓Hot Backup更完美
- PgSQL · 答疑解惑 · 表膨脹
- MySQL · 特性分析 · Index Condition Pushdown (ICP)
- MariaDB · 社區動態 · MariaDB on Power8
- MySQL · 特性分析 · 企業版特性一覽
- 數據庫內核月報 - 2015/11
- MySQL · 社區見聞 · OOW 2015 總結 MySQL 篇
- MySQL · 特性分析 · Statement Digest
- PgSQL · 答疑解惑 · PostgreSQL 用戶組權限管理
- MySQL · 特性分析 · MDL 實現分析
- PgSQL · 特性分析 · full page write 機制
- MySQL · 捉蟲動態 · MySQL 外鍵異常分析
- MySQL · 答疑解惑 · MySQL 優化器 range 的代價計算
- MySQL · 捉蟲動態 · ORDER/GROUP BY 導致 mysqld crash
- MySQL · TokuDB · TokuDB 中的行鎖
- MySQL · 捉蟲動態 · order by limit 造成優化器選擇索引錯誤
- 數據庫內核月報 - 2015/10
- MySQL · 引擎特性 · InnoDB 全文索引簡介
- MySQL · 特性分析 · 跟蹤Metadata lock
- MySQL · 答疑解惑 · 索引過濾性太差引起CPU飆高分析
- PgSQL · 特性分析 · PG主備流復制機制
- MySQL · 捉蟲動態 · start slave crash 診斷分析
- MySQL · 捉蟲動態 · 刪除索引導致表無法打開
- PgSQL · 特性分析 · PostgreSQL Aurora方案與DEMO
- TokuDB · 捉蟲動態 · CREATE DATABASE 導致crash問題
- PgSQL · 特性分析 · pg_receivexlog工具解析
- MySQL · 特性分析 · MySQL權限存儲與管理
- 數據庫內核月報 - 2015/09
- MySQL · 引擎特性 · InnoDB Adaptive hash index介紹
- PgSQL · 特性分析 · clog異步提交一致性、原子操作與fsync
- MySQL · 捉蟲動態 · BUG 幾例
- PgSQL · 答疑解惑 · 詭異的函數返回值
- MySQL · 捉蟲動態 · 建表過程中crash造成重建表失敗
- PgSQL · 特性分析 · 談談checkpoint的調度
- MySQL · 特性分析 · 5.6 并行復制恢復實現
- MySQL · 備庫優化 · relay fetch 備庫優化
- MySQL · 特性分析 · 5.6并行復制事件分發機制
- MySQL · TokuDB · 文件目錄談
- 數據庫內核月報 - 2015/08
- MySQL · 社區動態 · InnoDB Page Compression
- PgSQL · 答疑解惑 · RDS中的PostgreSQL備庫延遲原因分析
- MySQL · 社區動態 · MySQL5.6.26 Release Note解讀
- PgSQL · 捉蟲動態 · 執行大SQL語句提示無效的內存申請大小
- MySQL · 社區動態 · MariaDB InnoDB表空間碎片整理
- PgSQL · 答疑解惑 · 歸檔進程cp命令的core文件追查
- MySQL · 答疑解惑 · open file limits
- MySQL · TokuDB · 瘋狂的 filenum++
- MySQL · 功能分析 · 5.6 并行復制實現分析
- MySQL · 功能分析 · MySQL表定義緩存
- 數據庫內核月報 - 2015/07
- MySQL · 引擎特性 · Innodb change buffer介紹
- MySQL · TokuDB · TokuDB Checkpoint機制
- PgSQL · 特性分析 · 時間線解析
- PgSQL · 功能分析 · PostGIS 在 O2O應用中的優勢
- MySQL · 引擎特性 · InnoDB index lock前世今生
- MySQL · 社區動態 · MySQL內存分配支持NUMA
- MySQL · 答疑解惑 · 外鍵刪除bug分析
- MySQL · 引擎特性 · MySQL logical read-ahead
- MySQL · 功能介紹 · binlog拉取速度的控制
- MySQL · 答疑解惑 · 浮點型的顯示問題
- 數據庫內核月報 - 2015/06
- MySQL · 引擎特性 · InnoDB 崩潰恢復過程
- MySQL · 捉蟲動態 · 唯一鍵約束失效
- MySQL · 捉蟲動態 · ALTER IGNORE TABLE導致主備不一致
- MySQL · 答疑解惑 · MySQL Sort 分頁
- MySQL · 答疑解惑 · binlog event 中的 error code
- PgSQL · 功能分析 · Listen/Notify 功能
- MySQL · 捉蟲動態 · 任性的 normal shutdown
- PgSQL · 追根究底 · WAL日志空間的意外增長
- MySQL · 社區動態 · MariaDB Role 體系
- MySQL · TokuDB · TokuDB數據文件大小計算
- 數據庫內核月報 - 2015/05
- MySQL · 引擎特性 · InnoDB redo log漫游
- MySQL · 專家投稿 · MySQL數據庫SYS CPU高的可能性分析
- MySQL · 捉蟲動態 · 5.6 與 5.5 InnoDB 不兼容導致 crash
- MySQL · 答疑解惑 · InnoDB 預讀 VS Oracle 多塊讀
- PgSQL · 社區動態 · 9.5 新功能BRIN索引
- MySQL · 捉蟲動態 · MySQL DDL BUG
- MySQL · 答疑解惑 · set names 都做了什么
- MySQL · 捉蟲動態 · 臨時表操作導致主備不一致
- TokuDB · 引擎特性 · zstd壓縮算法
- MySQL · 答疑解惑 · binlog 位點刷新策略
- 數據庫內核月報 - 2015/04
- MySQL · 引擎特性 · InnoDB undo log 漫游
- TokuDB · 產品新聞 · RDS TokuDB小手冊
- PgSQL · 社區動態 · 說一說PgSQL 9.4.1中的那些安全補丁
- MySQL · 捉蟲動態 · 連接斷開導致XA事務丟失
- MySQL · 捉蟲動態 · GTID下slave_net_timeout值太小問題
- MySQL · 捉蟲動態 · Relay log 中 GTID group 完整性檢測
- MySQL · 答疑釋惑 · UPDATE交換列單表和多表的區別
- MySQL · 捉蟲動態 · 刪被引用索引導致crash
- MySQL · 答疑釋惑 · GTID下auto_position=0時數據不一致
- 數據庫內核月報 - 2015/03
- MySQL · 答疑釋惑· 并發Replace into導致的死鎖分析
- MySQL · 性能優化· 5.7.6 InnoDB page flush 優化
- MySQL · 捉蟲動態· pid file丟失問題分析
- MySQL · 答疑釋惑· using filesort VS using temporary
- MySQL · 優化限制· MySQL index_condition_pushdown
- MySQL · 捉蟲動態·DROP DATABASE外鍵約束的GTID BUG
- MySQL · 答疑釋惑· lower_case_table_names 使用問題
- PgSQL · 特性分析· Logical Decoding探索
- PgSQL · 特性分析· jsonb類型解析
- TokuDB ·引擎機制· TokuDB線程池
- 數據庫內核月報 - 2015/02
- MySQL · 性能優化· InnoDB buffer pool flush策略漫談
- MySQL · 社區動態· 5.6.23 InnoDB相關Bugfix
- PgSQL · 特性分析· Replication Slot
- PgSQL · 特性分析· pg_prewarm
- MySQL · 答疑釋惑· InnoDB丟失自增值
- MySQL · 答疑釋惑· 5.5 和 5.6 時間類型兼容問題
- MySQL · 捉蟲動態· 變量修改導致binlog錯誤
- MariaDB · 特性分析· 表/表空間加密
- MariaDB · 特性分析· Per-query variables
- TokuDB · 特性分析· 日志詳解
- 數據庫內核月報 - 2015/01
- MySQL · 性能優化· Group Commit優化
- MySQL · 新增特性· DDL fast fail
- MySQL · 性能優化· 啟用GTID場景的性能問題及優化
- MySQL · 捉蟲動態· InnoDB自增列重復值問題
- MySQL · 優化改進· 復制性能改進過程
- MySQL · 談古論今· key分區算法演變分析
- MySQL · 捉蟲動態· mysql client crash一例
- MySQL · 捉蟲動態· 設置 gtid_purged 破壞AUTO_POSITION復制協議
- MySQL · 捉蟲動態· replicate filter 和 GTID 一起使用的問題
- TokuDB·特性分析· Optimize Table
- 數據庫內核月報 - 2014/12
- MySQL· 性能優化·5.7 Innodb事務系統
- MySQL· 踩過的坑·5.6 GTID 和存儲引擎那會事
- MySQL· 性能優化·thread pool 原理分析
- MySQL· 性能優化·并行復制外建約束問題
- MySQL· 答疑釋惑·binlog event有序性
- MySQL· 答疑釋惑·server_id為0的Rotate
- MySQL· 性能優化·Bulk Load for CREATE INDEX
- MySQL· 捉蟲動態·Opened tables block read only
- MySQL· 優化改進· GTID啟動優化
- TokuDB· Binary Log Group Commit with TokuDB
- 數據庫內核月報 - 2014/11
- MySQL· 捉蟲動態·OPTIMIZE 不存在的表
- MySQL· 捉蟲動態·SIGHUP 導致 binlog 寫錯
- MySQL· 5.7改進·Recovery改進
- MySQL· 5.7特性·高可用支持
- MySQL· 5.7優化·Metadata Lock子系統的優化
- MySQL· 5.7特性·在線Truncate undo log 表空間
- MySQL· 性能優化·hash_scan 算法的實現解析
- TokuDB· 版本優化· 7.5.0
- TokuDB· 引擎特性· FAST UPDATES
- MariaDB· 性能優化·filesort with small LIMIT optimization
- 數據庫內核月報 - 2014/10
- MySQL· 5.7重構·Optimizer Cost Model
- MySQL· 系統限制·text字段數
- MySQL· 捉蟲動態·binlog重放失敗
- MySQL· 捉蟲動態·從庫OOM
- MySQL· 捉蟲動態·崩潰恢復失敗
- MySQL· 功能改進·InnoDB Warmup特性
- MySQL· 文件結構·告別frm文件
- MariaDB· 新鮮特性·ANALYZE statement 語法
- TokuDB· 主備復制·Read Free Replication
- TokuDB· 引擎特性·壓縮
- 數據庫內核月報 - 2014/09
- MySQL· 捉蟲動態·GTID 和 DELAYED
- MySQL· 限制改進·GTID和升級
- MySQL· 捉蟲動態·GTID 和 binlog_checksum
- MySQL· 引擎差異·create_time in status
- MySQL· 參數故事·thread_concurrency
- MySQL· 捉蟲動態·auto_increment
- MariaDB· 性能優化·Extended Keys
- MariaDB·主備復制·CREATE OR REPLACE
- TokuDB· 參數故事·數據安全和性能
- TokuDB· HA方案·TokuDB熱備
- 數據庫內核月報 - 2014/08
- MySQL· 參數故事·timed_mutexes
- MySQL· 參數故事·innodb_flush_log_at_trx_commit
- MySQL· 捉蟲動態·Count(Distinct) ERROR
- MySQL· 捉蟲動態·mysqldump BUFFER OVERFLOW
- MySQL· 捉蟲動態·long semaphore waits
- MariaDB·分支特性·支持大于16K的InnoDB Page Size
- MariaDB·分支特性·FusionIO特性支持
- TokuDB· 性能優化·Bulk Fetch
- TokuDB· 數據結構·Fractal-Trees與LSM-Trees對比
- TokuDB·社區八卦·TokuDB團隊