阅读 177

InnoDB的表空间

表空间是什么

表空间是表所占用的磁盘空间的抽象,当建立一个库时,会在数据目录下创建一个同名目录,当在库里建立一个表时,InnoDB会在目录下建立一个同名的.frm文件,用于存储表的结构,以及一个.idb文件,用于存储表中的数据. 这个.idb文件,就是该表的表空间的实际存在形式.

MySQL5.6.6之前所有的表都默认存在同一个表空间(系统表空间).

mysql 8中将表的元数据文件.frm移除,转而将表元数据存在数据字典,参见Removal of File-based Metadata Storage

MyISAM会建立.frm文件,用于存储表结构,.myd文件,存储表的数据,.myi文件,存储表的索引. 从产生的文件的不同,也可看出MyISAM与InnoDB在数据组织上的不同,前者数据与索引分开存储,而后者将数据存在索引中.

从上层概念的层面看,表空间是表的索引的集合,一个表由1个聚集索引和若干个二级索引组成,对表内数据的操作,本质都是在这些索引对应的B+树上进行增删改查. 从底层存储的层面看,表空间就是一个idb文件,文件内按16KB为单位划分出若干个,每64个页为一个区(extent),每256个区为一个. B+树的每一个结点用一个页来存储,存储叶子结点的页的集合称为叶子结点段,存储非叶子结点的页的集合称为非叶子结点段,可见每个索引由两个 段(segment) 组成.

每个页都有一个页号,4个字节,一个表空间最多可容纳2^32个页,一个页16KB,则可知表空间最大大小为64TB.

0902.png

表空间的分配

向表中插入一条记录,其实就是向表中每个索引插入记录,当向索引插入一条记录时,在叶子结点和非叶子节点都(可能)会插入记录,而当空间不足时,就需要分别为索引的两个段(叶子结点段和非叶子结点段)分配新的空间.

如果按页为单位为段分配空间,会导致叶子页相邻页相距较远,而后续对页节点做跨页区间扫描时产生随机IO.为了尽可能让叶子页形成的链表中的相邻页在物理磁盘上连续,一次为一个段分配一个区(64个页),以为将来的相邻页预留空间.另一方面,如果一个表没有数据或者数据很少时, 也按照分区为单位来分配空间的话, 那么即使一个只有聚簇索引的空表,也需要分配两个区,共2MB的空间(叶子页段和非叶子页段各分配一个区,一个区=64个页=64*16KB=1M).

为了避免数据很少时而分配过多的空闲空间,在一开始,以页为单位分配空间给索引的段, 随着表中数据的增多,索引的段占用的碎片页数量随之变大, 当一个段被分配了32个碎片页以后, 下一次分配就会变为以“区”为单位.

到这里,可以发现按页分配和按区分配,其实是“空间与时间”的权衡,为了追求节省空间,在表中数据很少时,设计InnoDB的人员选择用按页分配; 为了追求节省时间,当表中数据已经较多时(此时2M的空间只是表空间大小的零头),一次分配一个区(64个页),腾出63个都是空闲的页,为了将来相邻页可以在磁盘上连续存储,以得到更快的跨页区间扫描性能.

表空间的组织

表空间的组织其实就是分区的组织,一个分区可以直属于表空间(类似直辖市),也可以属于段(类似省级下辖的市).

直属于表空间分区可分为3类:

  • FREE,表示分区尚未开始使用,64个页均为空闲

  • FREE_FRAG,表示分区已经开始使用,还有空闲的页

  • FULL_FRAG,表示分区已经开始使用,64个页均已被使用

这3类分区在逻辑上被组织为3个链表,FREE LIST,FREE_FRAG LIST,FULL_FRAG LIST. 一开始,表中没有数据,此时为表空间分配一个分区,该分区状态为FREE,被挂在FREE LIST,之后,当插入数据时,从该分区中分配页给索引段,此时分区状态变为FREE_FRAG,被转移到FREE_FRAG LIST,当分区中的64个页都分配完时,则该分区的状态变为FULL_FRAG,被转移到FULL_FRAG LIST. 当一个段已经被分配满32个零散页后,就开始以完整的分区为单位为其分配空间了,被分配给段的分区的从Free链表中移除,并状态变为FSEG.

属于段的分区也被分为3类:

  • FREE,表示分区尚未开始使用,64个页均为空闲

  • NOT_FULL,表示分区已经开始使用,还有空闲的页

  • FULL,表示分区已经开始使用,64个页均已被使用

这3类分区在逻辑上也被组织为3个链表,FREE LIST,NOT_FULL LIST,FULL LIST,随着分区状态的改变,其在链表转换逻辑也同上,不再赘述.

即然是链表,自然要有头节点,并且每个节点都要有指向相邻节点的指针,那么上述6个链表在哪里维护这些状态呢?答案是存储在表空间,段,区的元信息里,这就需要引入表空间的结构了.

0903.png

表空间中每256个分区为一组,第0-255个分区为第一组,第256~511个为第二组,以此类推. 在每个组的头几个页面中存储了表,段,分区的一些元信息.

表的元信息

在第一组的第一个分区的第一个页(FSP_HDR类型)中,File Space Header部分存储了直属于表空间的3个分区链表的头节点,分别为下图中List Base Node for FREE LIST,LIST BASE NODE for FREE_FRAG LIST,LIST BASE NODE for FULL_FRAG LIST, 它们的结构都为List Base Node

0909.png

如下图,List Base Node,中存储了链表头尾节点地址

0906.png

段的元信息

在第一个分区的第3个页(INODE类型)中,用INODE Entry结构存储了表空间中每个段的元信息,

0911.png

INODE Entry的内部结构如下图,其中记录了段的3个链表的头节点,分别为List Base Node For FREE LIST,List Base Node For NOT_FULL LIST,List Base Node For FULL LIST,以及32个Fragment Array Entry,其中每个Entry 4个字节,刚好记录32个零散页的地址.

0907.png

分区的元信息

每个分组的第一个页中,存储了组内每个分区的元信息. 对于第一个组其第一个页为FSP_HDR类型,其他分组的第一个页均为XEDS类型,两者区别仅在于前者不仅存了分区元信息,还存了表空间元信息. 下图为XDES类型的页结构(FSP_HDR也类似):

0910.png

可以看到,XDES页中存256个XDES Entry,依次对应组内256个分区,每个XDES Entry都记录了一个分区的元信息,其结构如下:

0904.png

其中,List Node部分存储相邻节点的指针.

Q&A

  • Q: 如何知道表空间有哪些分区,以及分区分别处于什么状态?

    A: 直属于表空间的分区按3种不同状态,FREE(空闲),FREE_FRAG(部分页空闲),FULL_FRAG(已全部被占用),被放置在3个表分区链表,链表的头/尾结点存在FSP_HDR页的File Space Header中的Base List Node结构中,该结构中存储了指针,指向分区元信息的XDES ENTRY结构,该结构记录了相邻节点(XDES Entry)的地址,最终可以找到链表上的所有分区.

  • Q: 如何知道表空间有哪些段?

    A: 所有INODE类型的页中所存储的INODE Entry都对应一个段,FSP_HDR页的File Space Header中,有2个链表的基节点,分别为SEG_NODEES_FULL,SEG_NODE_FREE,串联了表空间内所有INode页,遍历这两个链表即可知道表空间内有哪些段.

  • Q: 如何知道段有哪些分区,以及分区分别处于什么状态?

    A: 每个段都有元信息INode Entry,其中存放了3个链表的头/尾节点,分区按状态不同,被放在了不同的链表里

  • Q: 如何知道段有哪些零散页? A: 通过INODE Entry中的Fragment Array,其长度为32,段所拥有的零散页就记录在数组元素里,每个元素(Fragment Entry)记录了一个零散页的页号(4个字节)

  • Q: 如何知道分区内的每个页的状态(空闲or被使用)

    A: 在分区元信息XDES Entry中的Page State Bitmap部分,使用01来表示分区内的64个页的状态.

  • Q: 如何知道每个页的空间使用情况?

    A: 如果是Index类型的页(其他类型应该差不多原理),Page Header部分的PAGE_HEAP_TOP属性,记录了还未使用的空间的最小地址,该地址之后就是空闲空间(除掉最末尾的File Trailer占用的8个字节)

  • Q: 如何知道索引对应的段在哪?

    A: 索引根结点页的Page Header的Page_BTR_SEG_LEAFPAGE_BTR_SEG_TOP,两者都为Segment Header结构,分别存储了叶子节点段和非叶子节点段的地址(也即INode Entry的地址)

系统表空间

系统表空间对应数据目录下的data1.idb文件,用于存储一些系统元信息,这其中就包含数据字典,数据字典由一系列内部系统表(internal system table)组成,其中最重要的4个表如下,它们被称为基本系统表(basic system table)

  • SYS_TABLE,存储所有表的元信息,如表在哪个表空间

    想象一下mysql在解析完一条sql后,如何知道sql中指定的表名是否存在,以及对应哪个表空间呢? 这就是SYS_TABLE的作用.

  • SYS_COLUMNS,存储所有列的元信息,如哪个表有哪些列,类型是什么等.

  • SYS_INDEXES,记录了所有索引的元信息,如索引是聚簇索引or唯一二级索引or普通二级索引,索引的根页面在哪里等.

  • SYS_FIELDS,记录所有建立了索引的列,其对应的索引ID(通过该ID可在SYS_INDEXES查出索引元信息)

可以发现,所有其他表都必须依赖于上述4个表,才能正常工作. 那么这4四个表自己的各种元信息又存在哪呢?答案是硬编码记录在一个固定的地方———表空间的第8个页,其结构如下:

0914.png

其中,Data Dictionary Header部分的属性含义如下:

  • Max Row/Table/Index/Space ID均为ID自增变量

  • Root of SYS_TABLES clust ibdex是表SYS_TABLES的聚簇索引根页面

  • Root of SYS_TABLES_IDS sec index表SYS_TABLE的二级索引根页面

  • Root of SYS_COLUMNS clust index 是表SYS_COLUMNS的聚簇索引根页面

  • Root of SYS_INDEXES clust index 是表SYS_INDEXES的聚簇索引根页面

  • Root of SYS_FIELDS clust index 是表SYS_FIELDS的聚簇索引根页面

有了以上信息,作为“表中之表”的这些数据字典表也就可以正常工作了.


作者:light0x00
链接:https://juejin.cn/post/7023687070816141319


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐