MySQL——InnoDB数据存储结构之页结构

本文深入探讨了MySQL InnoDB引擎的数据存储结构,重点介绍了页的概念和内部结构。页作为数据库读写的基本单位,通常大小为16KB,包含文件头、页头、最小最大记录、用户记录、空闲空间、页目录和文件尾等部分。页目录通过二分法加速查找,而页头则存储了页的状态信息。此外,文章还提到了页在数据库存储结构中的位置,如表空间、段和区的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MySQL——InnoDB引擎数据存储结构 页

1、数据库的存储结构——页

索引结构给我们提供了高效的索引方式,索引信息以及数据记录都是存储在页结构中。另一方面,索引是在存储引擎中实现的,MySQL服务器上的存储引擎负责对表中数据的进行读取和写入。不同存储引擎中存放数据的格式一般是不同的,甚至有的存储引擎比如Memory都不用磁盘来存储数据。

页的概述

数据库读写磁盘的基本单位是页(Page),数据库,无论是读一行,还是读取多行,都是将这些行所在的页进行加载。

因为记录是按照行来存储的,但是数据库的读取并不以行为单位,否则一次I/O操作只能处理一行数据,效率会很低。

lnnoDB将数据划分为若干个页,InnoDB中页的大小默认为16KB。

以页作为磁盘和内存之间交互的基本单位,也就是一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB的内容刷新到磁盘中。

页结构

不同的页可以不在物理结构上相连,只要通过双向链表相关联即可,每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表,每个数据页都会为存储在它里边的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。

页的大小

不同的DBMS的页大小不同。在MySQL的InnoDB中,默认页大小是 16KB。

查看默认页大小:

show variables like '%innodb_page_size%';

在这里插入图片描述

SQL Server中页的大小为 8KB,而在Oracle中我们用术语“块”(Block)来代表“页”,Oralce支持的块大小为 2KB,4KB,8KB,16KB,32KB 和 64KB。

页的上层结构

在数据库存储结构中,还存在表空间(tablespace),段(segment),区(extent),行(row)的概念

img

区(Extent)是比页大一级的存储结构,在InnoDB存储引擎中,一个区会分配64 个连续的页。因为InnoDB中的页大小默认是16KB,所以一个区的大小是64*16KB= 1MB。

段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在InnoDB中是连续的64个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。

表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。

2、页的内部结构

页如果按照类型划分的话,常见的有 数据页,系统页、Undo日志页和事务数据页等。数据页是我们最常使用的页。

数据页的16KB大小的存储空间被划分为七个部分,分别是文件头(File Header)、页头(Page Header)、最大最小记录(Infimum+supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer)。

页结构的大致示意图如下所示:

在这里插入图片描述

七个部分大致功能说明如下:

  • File Header 文件头:记录各种页的通用信息,比如上下页的页号,页类型,所有的数据页其实是一个双链表
  • Page Header 页头:记录本页存储记录的状态信息,比如本页记录数量,槽数量
  • Infimum + supremum 最小与最大记录,是两个虚拟的行记录
  • User Records 用户记录:真正存数据的地方:以链表的形式存储一条条行记录
  • Free Space 存数据空间中尚未使用的区域
  • Page Directory 页目录:页中某些记录的相对位置,用于提升查询效率
  • File Trailer 文件尾:刷盘时校验页是否完整

2.1、File Header 文件头和 File Trailer 文件尾

File Header 文件头

File Header 文件头构成:

名称大小描述
FIL_PAGE_SPACE_OR_CHKSUM4字节页的校验和(checksum),文件尾也有这个属性,用于自校验。为了快速比较、保证数据的完整性防止遭到破坏等,采用给这页加上校验和到页尾的时候做自校验。
FIL_PAGE_OFFSET4字节页号,每一个页都都有一个单独的页号,InnoDB通过页号可以定位到这个页
FIL_PAGE_PREV4字节上一个页的页号
FIL_PAGE_NEXT4字节下一个页的页号,保证了页之间是逻辑上的连续
FIL_PAGE_LSN8字节页面最后被修改的日志序列位置
FIL_PAGE_TYPE2字节该页的类型
FIL_PAGE_FILE_FLUSH_LSN8字节独立表空间中都是0
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4字节页属于哪个表空间

File Trailer 文件尾

File Trailer 文件尾(8个字节)构成:

  • 前4个字节代表页的校验和,这个部分和 File Header 中的校验和相对应
  • 后4个字节代表页面被最后修改时对应的日志序列位置(LSN),这个部分也是为了校验页的完整性的,如果首部和尾部的LSN值校验不成功的话,就说明同步传输过程出现了问题。

2.2、User Records(用户记录)和 Infimum + Supremum(最小最大记录)

页的主要作用是存储记录,所以“最大最小记录” 和 “用户记录” 部分占了页结构的主要空间。

User Records(用户记录)

User Records是用来存储数据的地方,简单来说就是怎么把每行数据摆在这个空间里。

当我们新建一个表的时候表中用户记录(用户记录)部分是空的,在外面插入一条记录后会被记录到其中,直到插入满是会把记录信息刷入到下一个页中,往复循环。

User Records 中的这些记录按照指定的行格式一条一条摆放在 User Records 部分,相互之间形成单链表。

记录行格式的记录头信息在摆放数据的过程中发挥了重要的作用,下面是记录头的各个属性:

  • delete_mask 1bit 标记该记录是否被删除,

    • 0 表示记录没有删除,
    • 1 表示记录被删除了
  • min_rec_mask 1bit B+数的每非叶子节点中的最小纪录数都会添加该标记,

    • 只有最小纪录数的min_rec_mask 值为1,
    • 其他别的记录min_rec_mask 值为0
  • n_owned 4bit 如果当前记录是组内最大记录,则代表槽内的记录数

  • heap_no 13bit 当前记录在本页中的位置信息

  • record_type 3bit 表示当前记录的类型,

    • 0表示普通记录,
    • 1表示B+树非叶子节点记录,
    • 2表示最小记录,
    • 3表示最大记录
  • next_record 16bit 表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。

    比如:第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节便是下一条记录的真实数据。

被删除的记录为什么还在页中存储呢?

你以为它删除了,可它还在真实的磁盘上。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后其他的记录在磁盘上需要重新排列,导致性能消耗。所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

关于heap_no值为0和1的记录

MySQL会自动给每个页里加了两个记录,由于这两个记录并不是我们自己插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录,一个代表最大记录。最小记录和最大记录的heap_no值分别是0和1,也就是说它们的位置最靠前。

Infimum + Supremum(最小最大记录)

比较记录的大小就是比较主键的大小。

InnoDB规定的最小记录与最大记录这两条记录的构造十分简单,都是由5字节大小的记录投信息和8个字节大小的一个固定的部分组成的:

在这里插入图片描述

定Infimum记录(也就是最小记录)的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是Supremum记录(也就是最大记录)。

2.3、Page Directory(页目录)和 Page Header(页面头部)

Page Directory(页目录)

现在有一个问题,我们要在一个页中查找指定的一条记录。除了从顺序查找还有更高效率的方法么?

Page Directory提供了解决方案:二分查找法。

InnoDB 会将一个页中的所有记录划分成若干个组,每组4-8个记录。将每个组最后一个记录相对于第一个记录的地址偏移量(可以定位到真实数据记录)提取出来存放在页中一个叫做Page Directory的数组中,数组中的元素就是这些地址偏移量,也称为槽(slot)。所以Page Directory就是由槽组成的。

所以在一个页中根据主键查找记录是很快的,步骤为两步:

  1. 二分法确定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。
  2. 通过记录的 next_record 属性遍历该槽所在的组中的各个记录

Page Header(页面头部)

为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫Page Header的部分,这个部分占用固定的56个字节,专门存储各种状态信息。

各字段如下:

名称占用空间大小描述
PAGE_N_DIR_SLOTS2字节页目录中的槽数量
PAGE_HEAP_TOP2字节还未使用的空间最小地址,该地址之后就是 Free Space
PAGE_N_HEAP2字节本页中的记录的数量(包括最大最小和标记为删除的记录)
PAGE_FREE2字节指向可重用空间的地址
PAGE_GARBAGE2字节已删除记录占用的字节数
PAGE_LAST_INSERT2字节最后插入记录的位置
PAGE_DIRECTION2字节最后插入的方向
PAGE_N_DIRECTION2字节一个方向连续插入的记录数量
PAGE_N_RECS2字节该页中记录的数量(不包括最大最小和标记为删除的记录)
PAGE_MAX_TRX_ID2字节修改当前页的最大事务ID,该值仅在二级索引中定义
PAGE_LEVEL2字节当前页在索引树(B+树)中的层级
PAGE_INDEX_ID8字节索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF10字节B+树叶子段的头部信息,仅在B+树的Root页定义
PAGE_BTR_SEG_TOP10字节B+树叶子段的头部信息,仅在B+树的Root页定义

假如新插入的一条记录的主键值比上一条记录的主键值大,我们说这条记录的插入方向是右边,反之则是左边。用来表示最后一条记录插入方向的状态就是PAGE_DIRECTION。

假设连续几次插入新记录的方向都是一致的,InnoDB会把沿着同一个方向插入记录的条数记下来,这个条数就用PAGE_N_DIRECTION这个状态表示。当然,如果最后一条记录的插入方向改变了的话,这个状态的值会被清零重新统计。

### 回答1: 确实,MySQL的varchar类型在InnoDB存储引擎中的存储结构比较复杂。在InnoDB中,每个记录都被存储为一个B+树节点,每个节点都有一个固定大小的,通常为16KB。 当一个varchar类型的列被插入到InnoDB表中时,它会被拆分成两个部分:一个是实际的数据,另一个是长度信息。长度信息会被存储在记录头中,而实际的数据会被存储在记录的数据中。 在InnoDB中,如果一个varchar类型的列的长度小于等于768个字节,那么它会被存储在记录的数据中。如果一个varchar类型的列的长度超过了768个字节,那么它会被存储在单独的中,并且在记录中只存储一个指向这个的指针。 此外,由于InnoDB使用了行级锁定,每个记录都需要存储一个事务ID,用于实现MVCC(多版本并发控制)。因此,在InnoDB中,每个记录头还需要存储一个6字节的事务ID和一个2字节的回滚指针。 综上所述,当使用varchar类型时,需要注意其实际数据的长度和存储引擎的存储结构,以便更好地设计表结构和查询语句。 ### 回答2: MySQL的varchar存储结构确实是相当深奥的。在InnoDB存储引擎中,varchar类型的数据存储在表的记录中,其存储结构会影响数据写入、存储空间占用和查询性能。 首先,varchar类型的数据在记录中是以变长字符串的形式进行存储的。这意味着,varchar字段占用的存储空间与其实际存储的数据长度相关,而不是固定的。相比之下,固定长度的数据类型(如char)在存储时会占用固定的存储空间,无论实际数据的长度是多少。 其次,varchar类型的数据在记录中的存储格式是由一个表示长度的字节和真实字符串数据构成的。这个长度字段用于指示存储的实际数据的长度,使得数据库可以根据需要动态地分配存储空间,从而节省了存储空间。 此外,在InnoDB存储引擎中,varchar字段的数据存储内部的某个位置,而不是直接存储在上。这是由于InnoDB采用了B+树的数据结构来组织数据,为了节省存储空间和提高数据访问效率,varchar字段的数据会被存储在叶子节点中。这样一来,在查询时可以更快地遍历和定位数据,提高查询性能。 综上所述,MySQL的varchar存储结构的深度体现在其变长存储方式、长度字段和数据存储位置等方面。了解和理解这些存储结构对于正确使用varchar类型的字段、优化存储空间和提高查询性能都是非常重要的。 ### 回答3: MySQL的varchar存储结构InnoDB引擎中确实是一个很深入的话题。InnoDB引擎是MySQL的默认引擎,它采用了B+树索引来存储数据。在InnoDB的记录存储结构中,varchar类型字段经过了一系列处理。 首先,InnoDB将每个记录分为固定长度部分和变长长度部分。varchar字段属于变长长度部分。对于varchar字段,MySQL会额外存储一个指针,指向数据存储区域。 其次,在实际存储varchar字段值时,InnoDB会使用两种方式。对于较短的varchar字段值,会直接将其存储在记录的数据域中。这样做的好处是可以减少额外的存储开销。 而对于较长的varchar字段值,InnoDB会将其存储在一个称为“Overflow Page”的额外存储空间中。Overflow Page的指针存储在记录的数据域中。Overflow Page与主记录有一个单独的物理连接。 另外,需要注意的是,在InnoDB中,varchar字段的长度是可变的,存储的最大长度由定义时的最大长度决定。这与char字段是不同的,char字段的长度是固定的。 总之,MySQL的varchar存储结构InnoDB引擎中是相对复杂的。它采用了不同的存储方式来处理不同长度的字段值,既保证了数据的存储效率,又满足了灵活性的要求。对于开发人员来说,了解varchar存储结构对于正确使用和优化数据库非常重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万里顾—程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值