【华为云MySQL技术专栏】InnoDB物理文件管理

举报
GaussDB 数据库 发表于 2024/12/20 11:33:37 2024/12/20
【摘要】 1. 背景介绍在生产环境中,客户常对InnoDB表的物理文件管理机制存在疑惑,特别是关于表数据被大量删除时产生的碎片页问题。具体来说,当InnoDB表中的大量数据被删除时,原本连续存储的数据页之间会留下空闲页,这些空闲页就像碎片一样,使得表文件变得不再紧凑,我们称之为碎片页。那么,为什么InnoDB表数据被大量删除时会产生碎片页?碎片页能否被新插入的数据,或者被其他表复用呢?本文将通过深入剖...

1. 背景介绍

在生产环境中,客户常对InnoDB表的物理文件管理机制存在疑惑,特别是关于表数据被大量删除时产生的碎片页问题。具体来说,当InnoDB表中的大量数据被删除时,原本连续存储的数据页之间会留下空闲页,这些空闲页就像碎片一样,使得表文件变得不再紧凑,我们称之为碎片页。

那么,为什么InnoDB表数据被大量删除时会产生碎片页?碎片页能否被新插入的数据,或者被其他表复用呢?

本文将通过深入剖析InnoDB的物理文件管理机制,从页、区、段、索引、表空间五个核心层面进行详尽阐述,从原理层面解答上述的这些疑问。

2. 页面

以MySQL 8.0.32版本为例,InnoDB的物理文件体系主要包括系统表空间文件(ibdata*)、用户表空间文件(*.ibd)、独立的undo表空间文件(默认为undo_*)、临时表空间文件(temp_*.ibt)、redo日志文件(#ib_redo*)以及Double write文件(默认为#ib_16384_*.dblwr)等。

在这些文件中,除了redo日志文件外,其余文件都是由InnoDB标准页面(Page)构成。通常情况下,一个页面大小为16KB,可以通过设置innodb_page_size参数,在初始化数据库时,将页面大小调整为4KB到64KB之间的任意大小。

在InnoDB存储引擎中,存在多种页面类型,各自承担着不同的功能角色。包括:

- 表空间首页(FIL_PAGE_TYPE_FSP_HDR),用于记录表空间的元信息;

- 用户数据存储页面(FIL_PAGE_INDEX),用于存放用户表的数据和主键索引;

- 回滚段页面(FIL_PAGE_UNDO_LOG),用于存储undo日志,支持事务的回滚;

- 段信息页面(FSP_SEG_INODES_PER_PAGE),用于管理数据文件中的段信息;

- 区信息页面(FIL_PAGE_TYPE_XDES),用于维护区的元数据;

- 二级索引修改缓存页面(FIL_PAGE_IBUF_BITMAP),用于缓存二级索引的更新操作。

尽管这些页面类型各异,但它们都共享相同的页面结构,包括通用页头(Fil Header)和通用页尾(Fil Trailer),其中记录的详细信息如图1所示:

图1 InnoDB存储引擎页面结构图

下面我们将重点介绍与用户数据直接相关的FIL_PAGE_INDEX类型,该类型通常被称为索引页类型。

索引页由通用页头(Fil Header)、索引页头(Page Header)、记录(Record)、空闲空间(Free Space)、页目录(Page Directory)、通用页尾(Fil Trailer)五个部分组成。其结构布局如图2所示。

图2 索引页结构图

索引页中,记录是从低地址向高地址生长,页目录则是由高地址向低地址生长。

2.1 记录

为了更好地理解索引页头和页目录,先介绍InnoDB索引页中记录的排列方式。

记录的格式如图3所示:更多内容可参考【华为云 MySQL技术专栏】MySQL8.0 Instant DDL源码实现

图3 记录格式图

记录头信息中包含了next_record, heap_no, delete_flag, n_owned等重要信息。其中,

next_record:所有记录在页面中按照插入顺序从前往后插入。

假设id为primary key(主键),且id为1,3,2的三条记录依次插入同一页面,那么在页面中三条记录的排列顺序也为1,3,2。这三条记录通过next_record属性相连,即id为1的记录next_record指向id为2的记录,id为2的记录next_record指向id为3的记录。

heap_no:记录在页面中的物理编号,上述id为1,3,2的三条记录的heap_no依次为N,N+1,N+2。

delete_flag:记录被删除后,并不是直接从页面中物理清除,而是标记上delete_flag标识,这样便于多版本并发控制服务(MVCC)。页面中被delete mark的记录也会通过next_record串联成链表,记录在页头中。当记录不再被MVCC需要时,它们将会被purge线程从页面中彻底清除。

n_owned:该属性与页目录相关,用于优化页内记录的访问,具体细节将在页目录小节中介绍。

为了方便对页内记录进行访问,每个索引页都添加了两条系统记录:infimum和supremum,它们分别代表虚拟的最小记录和虚拟的最大记录。这两条记录没有变长字段列表和NULL值列表,只包含记录头信息和真实数据。这两条记录固定在索引页头之后,heap_no分别是0和1的记录。从虚拟记录infimum开始,一直通过next_record指针访问到虚拟记录supremum,可以按逻辑大小遍历页内的所有记录。

2.2 目录

InnoDB的B+树索引在记录查询时,只能查询到数据页级别。假设一条记录的大小为50字节,一个16384字节大小的页面可以存储300条左右的记录,如果页内查询都通过next_record从infimum遍历到supremum,那么查询将非常低效。

为了解决页内记录查询低效的问题,页目录应运而生。页目录由一个个Slot组成,每个Slot占用两个字节,用于存储指向页面内记录的偏移量。每个Slot指向一条记录,其值即为该记录在页面中的相对位置。在这些记录中,被Slot指向的记录作为组长记录,其n_owned为管理的记录总数。值得注意的是,Slot的分配遵循逆向生长的机制,即从通用页尾(Fil Trailer)开始逆向生长,这样有助于高效利用页面空间,便于新记录的插入和现有记录的访问。

一个page至少有两个Slot,第一个Slot指向infimum记录,最后一个Slot指向supremum记录。第一个Slot只管理infumum,因此infimum的n_owned为1。在第一个和最后一个Slot中间的每个Slot,管理着4~8条记录,而指向supremum的最后一个Slot,管理着1~8条记录。

                                图4 页目录示意图

如图4所示,Slot1指向infimum,其n_owned为1。Slot2指向Rec5,Rec5管理Rec1-Rec5,其n_owned为5;Rec3指向Rec10,Rec10管理Rec6-Rec10,其n_owned为5。SlotN指向supremum,其n_owned可能值为1~8。

有了页目录,记录的查找过程显著优化。大概思路:首先,对Slot进行二分查找,定位到具体的Slot后,再在该Slot对应的记录范围内进行顺序查找,直至找到目标记录。

2.3 空闲空间

空闲空间是介于用户记录和页目录之间的一块连续的未被使用的内存。用户记录是从前往后生长,页目录则是从后往前生长。在空间足够时,系统会直接从空闲空间这里分配内存;当空间不足时,会通过重新整理页面内的记录,将碎片进行空间合并或者页面分裂,具体行为取决于InnoDB各场景下采取的具体策略。

2.4 索引页头

有了前三小节的介绍,下面对索引页头进行介绍。索引页头包含了页面记录数量、所属B+树层级、页面插入方向等信息,共14部分,详见图5

图5 索引页头结构图

其中,PAGE_BTR_SEG_LEAF(第13部分)和PAGE_BTR_SEG_TOP(第14部分)与段结构有关,将在后续的内容中介绍。

3.

所有的InnoDB页面按照区(Extent)为单位进行划分和管理。区对应着一片物理相邻的页面,不同的页面大小对应的区大小也不尽相同。对应关系如图6所示:

图6 区与页面大小关系图

通常情况下,页面大小默认为16KB,一个区是由64个物理相邻的页面组成。为了描述一个区的用途和使用情况,系统会为每个区分配一个特定的结构,即区描述结构(XDES entry),其大小为40字节。在XDES结构中,有一个状态字段(XDES状态)与段结构紧密相关,具体如下:

图7 区描述结构(XDES entry)图

所有的区描述结构被固定在区描述页(FIL_PAGE_TYPE_XDES或FIL_PAGE_TYPE_FSP_HDR)中。当页面大小为16KB时,从编号为0的页面开始,每16384个页面就有一个区描述页,用于描述后续的16384个页面的用途和使用情况,系统表空间及用户表空间的结构,如图8所示:

图8 系统表空间及用户表空间结构示意图

可见,区描述页面中有256个区描述结构(XDES entry),按照先后顺序与其后续的256个区一一对应。

4.

InnoDB为了组织起所有的区,在表空间的第一个页面,维护了三个区的链表:FSP_FREE,FSP_FREE_FRAG,FSP_FULL_FRAG。这三个链表分别将完全未被使用的区描述结构(XDES_FREE)、部分被使用的区描述结构(XDES_FREE_FRAG)和完全被使用的区描述结构(XDES_FULL_FRAG)串联起来。

段(Segmen)作为InnoDB中的一个逻辑分配单位,它不像区一样有明确的对应页面,而是根据使用需求动态地管理区和页面。为了节省空间,每个段都会先从表空间FREE_FRAG中分配32个碎片页(FSEG_FRAG_ARR)。当这32个页面不够使用时,段会按照以下原则进行扩展:

如果当前段的大小小于1个区,则扩展到1个区;

当表空间小于32MB时,每次扩展1个区;

当表空间大于32MB时,每次则扩展4个区。

段的页面分配逻辑,可参考fseg_alloc_free_page函数。

在为区段分配空闲的区时,如果表空间FSP_FREE上没有可用的区,系统则会为FSP_FREE重新初始化一些空闲区。表空间文件的拓展逻辑为:

当文件大小小于32个区时,每次拓展1个区的大小;

当文件大小超过32个区时,每次则拓展4个区的大小。

表空间的拓展逻辑,可参考fsp_get_pages_to_extend_ibd函数。

段与表空间的页面管理,类似于一套借还机制。段从表空间租借页面和完整的区,申请成功后,这些页面从表空间的三个区链表中移除,进入段的内部管理。当一个完整的区被划入段管理后,其状态从XDES_FREE变为XDES_FSEG。当段对于页面或者使用结束时,例如在表数据大量删除后,会将这些页面或区归还给表空间,这些页面或区会重新出现在FSP_FREE、FSP_FULL_FRAG、FSP_FULL三个链表中。

在段内部,为了精细化管理起这些从表空间申请到的区,同样采用了三个区链表机制:FSEG_FREE、FSEG_NOT_FULL、FSEG_FULL。这三个链表分别对应区完全未被使用、部分被使用、完全被使用的区描述结构,并被记录在段描述结构(Inode entry)中,详见图9:

图9 段描述结构图

段描述结构存储于FSP_SEG_INODES_PER_PAGE页面中,每个页面可以存放85个段描述结构。在表空间首页(FIL_PAGE_TYPE_FSP_HDR)中,维护了两个段描述结构链表:FSP_SEG_INODES_FULL和FSP_SEG_INODES_FREE。前者对应没有空闲段描述结构的段描述页链表,后者对应的至少有一个空闲段描述结构的段描述页链表。

5. 索引

系统表空间ibdata与用户表空间ibd文件中,真正构建起用户数据的结构是B+树。表中的每一个索引,都会对应一棵B+树。表的主键索引(clustered index)对应的B+树的叶子节点上的每行,记录了所有用户列的数据。二级索引(secondary index)的叶子节点中的每行,记录了其二级索引键值和对应的主键索引键值。

每棵B+树是通过两个段来管理数据页:一个管理叶子节点(leaf segment),一个管理非叶子节点(non-leaf segment)。这两个段的段描述结构地址名称为PAGE_BTR_SEG_LEAF和PAGE_BTR_SEG_TOP,记录在B+树的根节点(root page)中。

需要注意的是:当B+树只有一层时,唯一的B+树页面既是根节点又是叶子节点,该页面仍然会分配在非叶子节点段的第一个碎片页上。

图10 索引结构图

图10展示了一个索引及其涉及的段和区的结构关系。从图中可以看到,每个索引对应一棵B+树,B+树的根节点中记录了叶子节点段和非叶子节点段的地址,通过该地址可以查询到相应段的段描述结构信息。在段描述结构中,存储了32个碎片页的信息和 FSEG_FREE、FSEG_NOT_FULL、FSEG_FULL这三个区描述结构链表的信息。区描述结构链表中记录了区的类型和使用情况等信息,并且在页面大小为16KB时,有连续的64个物理相邻的页面与之对应。

6. 表空间

InnoDB的表空间(Tablespace)是用于存储InnoDB数据和索引的宏观逻辑存储结构。InnoDB表空间中既包含前文介绍的索引、区描述页、段描述结构链表外,还包含用于冗余备份的表元数据信息的SDI 索引信息。在数据字典损坏的情况下,SDI 索引信息可以用于恢复表定义。

InnoDB支持多种表空间类型,主要包括:

系统表空间(System Tablespace)

InnoDB的默认表空间,所有的表和索引数据最初都存储在一个共享的表空间文件ibdata中。系统表空间存储了用于维护页面持久化的事务系统信息、undo日志、记录二级索引修改缓存、用于防止部分写的双写系统(Double write)(仅限于MySQL 8.0.20版本之前)等关键信息。

此外,在MySQL 5.7版本中,系统表空间还包含InnoDB的内部数据,如数据字典;在MySQL 8.0版本中进行了数据字典重构,并将其移动到了mysql.ibd文件中。

通用表空间(General Tablespace)

用户可以创建和管理通用表空间,用于存储一个或多个InnoDB表。这种模式提供了更灵活的表空间管理。

独立表空间(File-Per-Table Tablespace)

每个InnoDB表都有一个单独的表空间ibd文件。这种模式可以更灵活地管理和备份单个表的数据。可以通过配置选项innodb_file_per_table来启用独立表空间。

在使用系统表空间时,所有用户表共享系统表空间,碎片页能在不同的表之间实现复用;在使用通用表空间时,碎片页可以在共享相同表空间的几个表之间进行复用;而在使用独立表空间时,一个表的碎片页面不能被另一个表复用。

当碎片页明显失衡的时候,可以尝试使用Optimize table table_name或者ALTER TABLE table_name ENGINE = InnoDB命令,手动在新创建的表中重写一份InnoDB页面紧凑的数据来重建表。通过这些操作,可以有效地管理存储空间,减少碎片,提升数据库的性能和存储效率。

7. 总结

InnoDB的文件结构自下而上包括页面(Page/Block)、区(Extent)、段(Segment/Inode)、索引(Index)、表空间(Tablespace)等五个层次,每一层都扮演着关键角色,如:

页面,最基本的物理存储单位,所有页面具有相同的页首和页尾,便于数据页的统一访问;

,通常由连续的64个物理连续的页面组成,便于存储空间的管理和分配;

,数据管理的逻辑单元,负责以页或区为单位,高效地管理页面的申请和释放;

索引,由叶子节点段和非叶子节点段组成,提供高效的数据访问路径;

表空间,包括多个索引以及各类保障数据库的正常运行和性能优化的关键信息,确保了数据的完整性和系统的高效运行。

InnoDB是内部存储设计相当复杂的存储引擎,它既能够保证数据的可靠性和完整性,还能提供出色的性能和可扩展性,满足了高负载场景下的应用需求。

引用:https://blog.jcole.us/2013/01/07/the-physical-structure-of-innodb-index-pages/


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。