MySQL下InnoDB表的存储结构
1 .idb文件和表空间(TableSpace)
当执行CREATE DATABASE语句的时候,其实会在mysql的datadir目录下创建一个和数据库名一样的文件夹,这样就可以自然的实现不同之间的库隔离。创建表(CREATE TABLE)命令执行的时候,会在相对应的库文件下,默认的的为每个表创建两个文件: 表名.frm, 表名.idb。
.frm后缀名的文件: 存储表的定义信息,包括字段名,字段的类型等信息。
.idb后缀名的文件: 存储数据内容,也就是记录用户插入表中的数据。
在MySQL的内部,统称.idb文件为TableSpace(表空间),并且为每个TableSpace分配一个唯一的标识:space_id,通过space_id范围,区分不同类型的表:
Table Space ID 分布
0x0 : SYSTEM_TABLE_SPACE
0x1 ~ 0xFFF9E108: USER SPACE
0xFFF9E108 ~ 0xFFFFFB88: session temp table
0xFFFFFF70 ~ 0xFFFFFFEF: undo tablespace ID
0xFFFFFFF0: redo log pseudo-tablespace
0xFFFFFFF1: checkpoint file space
0xFFFFFFFD: innodb_temporary tablespace
0xFFFFFFFE: data dictionary tablespace
0xFFFFFFFF : invalid space
InnoDB下的表数据组织结构形式是通过BTree来组织存储,而每一行的数据就是存储在BTree的叶子节点中。在创建表的时候,如果指定了主键索引列,此时默认表的是数据内容将会以聚簇索引的方式组织,如果没有指定主键,表中会自动添加一个列ROW_ID,通过该列生成聚簇索引来存储表中的数据。
2 .idb文件的组成结构
一个TableSpace在逻辑上由多个不同类型的Segment构成,Segment是逻辑上的划分,并不实际存储在文件系统中,Segment从业务功能上可以分为: Leaf Node Segment, Non-Leaf Node Segment, Rollback Segment。一个TablesSpace每种类型的Segment至少是大于等于一的。而组合Segment实际上是由多个Extend构成,Extent负债组织Page。Page是MySQL中最小的存储单元,一个Extend默认由64个Page组成,而一个Page的默认的大小为16kb,也就是说一个Extent大概有1M。
默认的TableSpace结构:
从上图的结构可以看出,Tablespace的最小存储单位还是Page,其下面的每个page都有一个唯一的标识Page_no,在创建表空间的时候,默认会创建4个特殊的Page,分别是Page0, Page1, Page2, Page3。 他们负责存储该Tablespace的元数据信息,包括但不局限于,总共Page数,可用Page数,索引信息,BTree的ROOT节点,Extend数量等。
2.1 page0介绍
Page0即”.ibd”文件的第一个Page,这个Page是在创建一个新的Tablespace的时候初始化,类型为FIL_PAGE_TYPE_FSP_HDR,这个Page用来跟踪后续256个Extent(约256M)的空间管理。每个Extent Entry占用40字节,总共分配出了256个Extent Entry,所以Page0和Extent描述页只管理后续256个Extent。也就是说这里是通过Extent Entry去管理文件系统中的Extent。
page0的数据结构图:
FSP Header数据结构:
在这个结构中,存储这个Tablespace中的总page数量,可用的page数量等,其中最主要的信息就是几个用于描述Tablespace内所有Extent和INode的链表,当InnoDB在写入数据的时候,会从这些链表上进行分配或回收Extent和Page,便于高效的利用文件空间。
XDES Entryd的数据结构:
个XDES中又会使用两个字节来描述这个Extent下的每个Page的状态。
2.2 page2(INode Page)介绍
Page2的数据结构如下:
在INode Page的每一个INode Entry对应一个Segment,结构如下:
InnoDB通过Inode Entry来管理每个Segment占用的Page,Inode Entry所在的inode page有可能存放满,因此在Page0中维护了Inode Page链表。
Page0中维护了表空间内Extent的FREE、FREE_FRAG、FULL_FRAG三个Extent链表,而每个Inode Entry也维护了对应的FREE、NOT_FULL、FULL三个Extent链表。这些链表之间存在着转换关系,以便于更高效的利用数据文件空间。
当用户创建一个新的索引时,在InnoDB内部会构建出一棵新的btree(btr_create),先为Non-leaf Node Segment分配一个INode Entry,再创建Root Page,并将该Segment的位置记录到Root Page中,然后分配Leaf Segment的Inode entry,也记录到root page中。