0%

《MySQL是怎样运行的》(二)

数据页

存储结构

MySQL中有不同的存储引擎,其存储方式均有所不同,以下仅以InnoDB引擎为例

  • 读取方式:页 InnoDB将数据库中的数据划分为若干页,每次进行数据存取是都以页为单位,页的大小是16KB.每一个页面上以记录的形式存放着相关数据,每条记录又可分为真实数据、额外数据和隐藏列(以COMPACT行格式为例),额外数据是用来辅助存储和读取信息的,包括变长字段长度列表、NULL值列表和记录头信息。
    1. 变长字段长度列表 常见的变长字段类型包括VARCHAR、TEXT类型等,这些数据类型的长度是不固定的,为了从记录中准确读取它的内容,就需要记录它所占据的字节数。

      • 变长字段的长度是按逆序存放的。例如变长字段1占据3字节,字段2占据8字节,则变长字段长度列表的存放就是0x080x03。
      • 假设某个字段L是VARCHAR(m)类型,采用的字符集最多需要使用w字节来表示一个字符,且其真实占用的字节数是L。当m×w的乘积结果小于等于255,则其只需要用1个字节来存放它的长度;当乘积结果大于255且L也大于127,则需要用2两个字节来存放它的长度,否则用1个字节即可。
      • 变长字段长度列表只存储值为非NULL的变长字段列。
    2. NULL值列表 该列表仅在表中允许存储NULL值的列存在的时候存在。其值等同于对应列按整数个字节逆序存放,并且用二进制表示。例如有三个列允许NULL值存在,且仅有第二和第三列为NULL,则其对应的NULL值列表的二进制表示为00000110。由于仅有三个列,需要1个字节来存放即可,高位用0补全,同时考虑到第二和第三列为NULL,正常情况为0 1 1,根据COMPACT行格式的存放规则,需要逆序存放,故得到的结果为00000110,即0x06。

    3. 记录头信息 该部分固定由5字节组成,用于描述记录的一些属性。此处仅列出比较重要的字段,不再多做赘述,可以查找资料了解即可。

      名称 大小(bit) 作用
      n_owned 4 组中的领头节点记录该组中的记录条数
      deleted_flag 1 标记该记录是否被删除
      min_rec_flag 1 B+树中每层非叶子节点的最小的目录项
      heap_no 13 当前记录在堆中的相对位置
      record_type 3 当前记录的类型
      next_record 16 下一条记录的位置
    4. 隐藏列 包括row_id、trx_id和roll_pointer。row_id非必须,只有表中没有定义主键,也没有不允许存储NULL值的UNIQUE键时,才会添加该字段,占用6字节。trx_id时必须的,占用6字节,属于事务id。roll_pointer也是必须的,占用7字节,用于进行事务回滚。

  • 溢出列 当某条记录所占空间过大时,在记录的真实数据处只会存放该字段的部分数据,然后用20字节指向剩余数据存放页的位置。

数据页结构

  • 数据页大小为16KB,主要可以分为七大部分,分别是File Header \ Page Header \ Infimum + Supremum \ User Records \ Fres Space \ Page Directory \ File Trailer.

    模块 占用空间(字节) 作用
    文件头 38 页的通用信息
    页面头 56 数据页的专有信息
    页面的最大记录和最小记录 26 两个虚拟记录
    数据(用户记录) 不定 用户存储的数据
    空白区域 不确定 页面中尚未使用的空间
    页目录 不确定 页面中某些记录的相对位置
    文件尾 8 校验页的完整性
  • 数据相关操作

    • - 往用户记录空间插入一条记录
      1. 每个页中都有两条默认插入的记录,分别是最小记录Infimum和最大记录Supremum,记录的大小通过主键的大小来确定,这两条是默认最大和最小的,但这两条记录存放在用户记录区域的靠前部分。
      2. 页面中所有记录都会分组,Infimum所在的组只能有1条记录,即它自己,Supremum所在的记录数量限制在1-8条,其余组的记录数量限制在4-8条。新增的记录从最近的比它大的记录所在的组开始插入。当组中记录的数量达到9条,从中间划分,生成一个新的组,前一个组的大小为4,后一个组的大小为5。
    • - 从用户记录空间删除一条记录
      1. 被删除的记录并不会直接从磁盘中删除,而是修改deleted_flag,其值为1时表示被删除,同时修改其next_record值为1,表示没有下一条记录。
      2. 所有被删除的记录会组成一个垃圾链表,按照主键由小到大的顺序链接,这些链表占用的空间被称为可重用空间。
    • - 从用户记录空间查询一条记录
      1. 每一组中最后一条记录的真实数据与该页开头的偏移量称为槽,按照组的逆序排列在页目录中,且从靠近文件尾的部分开始存储,每个槽占用2个字节。
      2. 按照二分法,low = 槽0, high = 槽n,先计算中间的槽的主键值(对应该组中最大的记录的主键值),然后修改low或者high。直到确认目标主键在哪一组中,遍历该组即可。(每一组最多只有8条记录,遍历速度很快)
  • 记录头信息

    1. n_owned : 每个页面中的记录会分为若干个组,组内最大的记录会记录该组内有多少条记录
    2. heap_no : 存放在页面前面的记录heap_no值偏小,每新生成一条记录的存储空间,其值都比物理位置在它前面的那条记录的heap_no值大1。heap_no从2开始,因为0和1分别指代了Infimum和Supremum。
    3. record_type : 0表示普通记录,1表示B+树非叶子节点的目录项记录,2表示Infimum,3表示Supremum。
    4. next_record : 当前记录的数据部分到下一条记录的数据部分。注意每条记录由记录头部分和数据部分组成,这个指向的是数据部分的开头,这样的好处是向后可以遍历数据,向前可以遍历该记录的记录头。当这个值为正数,说明下一条记录(指按主键排列的下一条记录,不是插入顺序的下一条记录)在当前记录的后面,反之在前面。规定Infimum记录的下一条记录就是当前页面主键值最小的记录,Supremum记录的next_record值为0,表示没有下一条记录。
  • 页面头
    页面头是页结构的第二部分,占用56字节,其各个部分的作用如下表所示。

    名称 占用空间(字节) 作用
    PAGE_N_DIR_SLOTS 2 页目录中的槽数量
    PAGE_HEAP_TOP 2 空闲空间起始地址
    PAGE_N_HEAP 2 前1比特表示本记录是否为紧凑型的记录,后15bit表示本页中记录的数量,包括Infimum和Supremum,以及删除的记录)
    PAGE_FREE 2 已删除的记录链表中起始记录所在的偏移地址
    PAGE_GARBAGE 2 已删除的记录占用的字节数
    PAGE_LAST_INSERT 2 最后插入的记录的位置
    PAGE_DIRECTION 2 记录插入的方向(新插入的记录的主键比上一条大或者小,前者认为是向右,否则向左)
    PAGE_N_DIRECTION 2 一个方向连续插入的记录数量(仅记录最新值)
    PAGE_MAX_TRX_ID 8 修改当前页的最大事务id,该值仅在二级索引页面中定义
    PAGE_LEVEL 2 当前页在B+树中所处的层级
    PAGE_INDEX_ID 8 当前页属于的索引id
    PAGE_BTR_SEG_LEAF 10 B+树叶子节点段的头部信息,仅在B+树的根页面中定义
    PAGE_BTR_SEG_TOP 10 B+树非叶子节点段的头部信息,仅在B+树的根页面中定义
  • 文件头
    文件头是页结构的第一部分,占用38字节,其各个部分的作用如下表所示。

    名称 占用空间(字节) 作用
    FIL_PAGE_SPACE_OR_CHKSUM 4 在MySQL4.0.14之后的版本中,该属性表示页的校验和
    FIL_PAGE_OFFSET 4 页号
    FIL_PAGE_PREV 4 上一页的页号
    FIL_PAGE_NEXT 4 下一页的页号
    FILE_PAGE_LSN 8 页面被最后修改时对应的LSN值(Log Sequence Number,日志序列号)
    FIL_PAGE_TYPE 2 该页的类型
    FIL_PAGE_FILE_FLUSH_LSN 8 仅在系统表空间的第一个页中定义,代表文件至少被刷新到了对应的LSN值
    FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 页属于哪个表空间

    其中FIL_PAGE_TYPE表示某个页面的类型,包括日志页、溢出页等,其可能的值见下表。

    类型名称 类型
    FIL_PAGE_TYPE_ALLOCATED 0x0000 新分配未使用
    FIL_PAGE_UNDO_LOG 0x002 undo日志页
    FIL_PAGE_INODE 0x003 存储段信息
    FIL_PAGE_IBUF_FREE_LIST 0x004 Change Buffer空闲列表
    FIL_PAGE_IBUF_BITMAP 0x005 Change Buffer的属性
    FIL_PAGE_TYPE_SYS 0x006 系统数据
    FIL_PAGE_TYPE_TRX_SYS 0x007 事务系统数据
    FIL_PAGE_TYPE_FSP_HDR 0x008 表空间头部信息
    FIL_PAGE_TYPE_XDES 0x009 存储区的属性
    FIL_PAGE_TYPE_BLOB 0x00A 溢出页
    FIL_PAGE_INDEX 0x45BF 索引页,也就是我们说的数据页
  • 文件尾

    1. 文件尾是页的最后一部分,占用8个字节。主要用于进行页面的校验。
    2. 文件尾的前4个字节是该页面的校验和,正常情况下其值等于文件头中的校验和。如果该页面被修改,会重新计算这两个部分的校验和。当该页面被刷新到磁盘中时,由于文件头在前,其值会被先写入磁盘。如果刷新页面的过程中发生了断电,重启服务后只需比较页面中的两个校验和即可,如果两个校验和相等,说明该页刷新成功,否则说明该页在刷新过程中发生了错误。
    3. 文件尾的后四个字节表示最后修改时对应的LSN的值,正常情况下与文件头中的FILE_PAGE_LSN相等。