InnoDB 的文件存储格式

2023-2-5|2023-6-7
麦兜
麦兜
type
Post
status
Published
date
Feb 5, 2023
slug
summary
tags
数据库
InnoDB
category
学习思考
password
icon

基本格式

  1. 表文件后缀:ibd
  1. 除了系统表,其他用户表一个Space对于一个Table
  1. Table 资源理方式为 Space→Segment →Extent →Page
  1. 默认一个Page为16kb (可以设置)
  1. Extent :64个连续Page ,资源管理最小单位。
  1. XDES(extent descriptor):用于描述 extent
  1. Segment:描述索引所占用的资源逻辑链表
  1. Inode Page: 里面存放多条 inode item 用于描述 segment
  1. 物理文件可能存储表(索引),UNDOLOG 等。
源码注释 innobase/dict/dict0crea.cc ,在创建新的表会创建4个Page.
/* We create a new single-table tablespace for the table.We initially let it be 4 pages: - page 0 is the fsp header and an extent descriptor page, - page 1 is an ibuf bitmap page, - page 2 is the first inode page, - page 3 will contain the root of the clustered index ofthe table we create here. */
page 0是用于描述整个表文件的page,page 1未知,page 2 用于管理表segment,page3聚集索引。

FSP Herader Page

notion image
 
  • FSP_SIZE:表空间大小,以Page数量计算
  • FSP_FREE_LIMIT:目前在空闲的Extent上最小的尚未被初始化的Page的Page Number
  • FSP_FREE:空闲extent链表,该extent内所有page均未被使用。
  • FSP_FREE_FRAG:链表中的每一项为xdes,该extent内有部分page未被使用。
  • FSP_FULL_FRAG:链表中的每一项为xdes,该extent内所有Page均已被使用
  • FSP_SEG_ID:下次待分配的segment id,每次分配新segment时均会使用该字段作为segment id,并将该字段值+1写回
  • FSP_SEG_INODES_FULL:每一项为inode page,该链表中的每个inode page内的inode entry都已经被使用
  • FSP_SEG_INODES_FREE:链表中的每一项为inode page,该链表中的每个inode page内上有空闲inode entry可分配
  • FSP Herader Page里面包含256个 XDES Entry。一个XDES Page为256个 XDES Entry用于监控256个Extent。所以每隔256个Extent便需要一个XDES Page。
 
XDES
Desc
File Segment ID (8)
属于某个段
List node for XDES list (12)
XDES的双链表
State (4)
NOT_INTIED,FREE,FREE_FRAG,FULL_FRAG,XDES_FSEG,XDES_FSEG_FRAG
Page State Bitmap (16) 2 bits per page,1=free,2=clean
notion image
每个extent对应了一个描述它的xdes,且xdes存储在XDES_PAGE之中,每个XDES_PAGE内可存储256个xdes结构,用来描述其后连续256个extent的情况,因此,一旦我们知道了某个extent的起始page no,便可以反推出其对应的xdes所在的page,xdes_calc_descriptor_page()
 

Inode Page

每个inode对应一个segment。每个inode page默认存储FSP_SEG_INODES_PER_PAGE(85)个inode。每个索引使用2个segment,分别用于管理叶子节点和非叶子节点。
 
inode page由page header和inode entry组成,page header为38字节。inode为192字节。
Macro
bits
Desc
FSEG_INODE_PAGE_NODE
12
INODE page在fsp header的某个链表节点,记录前后inode page的位置。该链表为header page的FSP_SEG_INODES_FULL或FSP_SEG_INODES_FREE。
Inode Entry 0
192
inode entry
Inode Entry 1
192
inode entry
……
Inode Entry 84
192
inode entry

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上所有page均空闲的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
存储Page Number。属于该Segment的frag page。总是先从全局分配独立的Page,当填满32个数组项时,就在每次分配时都分配一个完整的Extent,并在XDES PAGE中将其Segment ID设置为当前值
……
……
FSEG_FRAG_ARR 31
4
Segment的第32个Frag Page

文件链表

例如FSP_FREE是一个文件链表,有一个BaseNode包含整个链表元素长度,并且指向第一个元素First和最后一个元素Last。 除了BaseNode多了一个4 bytes的长度标记,其他的元素都具有相同的数据结构,6 bytes分为两部分: 4 bytesPage Number, 2 bytesPage在整个Tablespace的偏移
Base/Node
First/Prev
Page Number(4)
Offset (2)
Base
Length(4)
Last/Next
Page Number(4)
Offset (2)

Q&A

为什么使用Segment、Extent这种方式管理文件资源?
答:使用Segment可以更容易管理那些索引使用了那些资源。使用Extent而不是Page,是因为随着数据增长分配Extent的IO开销比Page小。

创建Segment

实际上创建索引就是创建Segment,然后往索引插入数据就是在Segment分配Page写入数据。
//创建Segment片段代码 /*!< 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 */ buf_block_t *fseg_create_general(...page_no_t page) { //首先去FSP_SEG_INODES_FREE找空闲的Inode Page space_header = fsp_get_space_header(space_id, page_size, mtr); inode = fsp_alloc_seg_inode(space_header, mtr); //seg_id 自曾。 mlog_write_ull是开启事物修改 seg_id = mach_read_from_8(space_header + FSP_SEG_ID); mlog_write_ull(space_header + FSP_SEG_ID, seg_id + 1, mtr); mlog_write_ull(inode + FSEG_ID, seg_id, mtr); //初始化 flst_init(inode + FSEG_FREE, mtr); flst_init(inode + FSEG_NOT_FULL, mtr); flst_init(inode + FSEG_FULL, mtr); //初始化segment的frag page数组为空(未分配任何page) for (i = 0; i < FSEG_FRAG_ARR_N_SLOTS; i++) { fseg_set_nth_frag_page_no(inode, i, FIL_NULL, mtr); } if(page==0) { //FSP_Header 获取Free Page。 fsp_alloc_free_page() block = fseg_alloc_free_page_low(space, page_size, inode, 0, FSP_UP, RW_SX_LATCH, mtr, mtr IF_DEBUG(, has_done_reservation)); } return block; }
fsp_alloc_seg_inode
/** Allocates a new file segment inode. @return segment inode, or NULL if not enough space */ static fseg_inode_t *fsp_alloc_seg_inode( fsp_header_t *space_header, /*!< in: space header */ mtr_t *mtr) /*!< in/out: mini-transaction */ { buf_block_t *block; page_t *page; fseg_inode_t *inode; page_no_t n; //如果如果FSP_SEG_INODES_FREE链表为空,尝试分配一个页面到 FSP_SEG_INODES_FREE if (flst_get_len(space_header + FSP_SEG_INODES_FREE) == 0 && !fsp_alloc_seg_inode_page(space_header, mtr)) { return (nullptr); } const page_size_t page_size(mach_read_from_4(FSP_SPACE_FLAGS + space_header)); const page_id_t page_id( page_get_space_id(page_align(space_header)), flst_get_first(space_header + FSP_SEG_INODES_FREE, mtr).page); block = buf_page_get(page_id, page_size, RW_SX_LATCH, UT_LOCATION_HERE, mtr); buf_block_dbg_add_level(block, SYNC_FSP_PAGE); fil_block_check_type(block, FIL_PAGE_INODE, mtr); page = buf_block_get_frame(block); n = fsp_seg_inode_page_find_free(page, 0, page_size, mtr); //从inode page 分配一个inode inode = fsp_seg_inode_page_get_nth_inode(page, n, page_size, mtr); //如果inode page满了就移动到FSP_SEG_INODES_FULL //if (!mach_read_from_8(inode + FSEG_ID)) { /* This is unused */ return (i); } if (FIL_NULL == fsp_seg_inode_page_find_free(page, n + 1, page_size, mtr)) { /* There are no other unused headers left on the page: move it to another list */ flst_remove(space_header + FSP_SEG_INODES_FREE, page + FSEG_INODE_PAGE_NODE, mtr); flst_add_last(space_header + FSP_SEG_INODES_FULL, page + FSEG_INODE_PAGE_NODE, mtr); } return (inode); }
 
InnoDB 的索引页结构关于 TCP 协议 “粘包和拆包” 的见解。