持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情
前言回顾
在前几篇文章中,学习了行结构compact和数据页结构的组成,在这一篇中,将学习行记录是如何在页中存储的。希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教。往期文章参考:
页存储行记录流程
在学习这部分前我们先回顾一下数据页结构相关的内容,数据库管理存储空间的基本单位是页,一个页中可以存储多个行记录,数据页被划分为7个部分结构示意图如下:
当我们插入记录的时候会按照指定的行格式存储到User Records部分,在创建一个新页时,其实并没有User Records这部分,每当我们插入一条记录,都会从Free Space部分(也就是尚未使用的存储空间)申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被用完后,也就表示这个页使用完了,如果有新的记录插入,就需要申请新的页了。
事实上User Records管理这些记录并非如此简单,要具体了解这个过程还得从行格式的变化说起。
行记录信息
我们先回顾一下行记录头信息的内容。
行记录与行记录之间的关系与行记录与页之间的关系主要存在于头信息。
- 预留位1、2: 暂未使用
- delete_mask: 标记该记录是否被删除
- min_rec_mask: B+树的每层非叶子节点中的最小记录都会添加该标记
- n_owned: 当前记录拥有的记录数
- heap_no: 当前记录在记录堆中的位置信息
- record_type: 当前记录类型;0-普通记录,1-B+树非叶子节点记录(即所谓的目录项记录),2-最小记录,3-最大记录
- next_record: 下一条记录的相对位置
其中next_record非常重要,它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量,这其实是个链表,可以通过一条记录找到它的下一条记录;下一条记录指的并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。
注意: 行记录是有大小的,数据页是根据主键大小来排序的,没有主键的话,会根据自动生成的rowid来作为主键排序。
我们通过举例看一下。
最小记录和最大记录
在页中还定义了两条伪记录分别为最小记录和最大记录,都是由5字节大小的记录头信息,和8字节大小的一个固定的部分组成,被单独存放在Infimum + Supremum部分。
可以理解为单向链表的链表头、链表尾,最小记录的下一条记录,就是本页主键值最小的用户记录,本页中主键值最大的用户记录,下一条记录就是最大记录。
删除记录
数据的操作除了插入还有删除,如果从中删除掉一条记录,我们来看下这个链表是怎么变化的,比如我们把第二条数据删除,其结构示意图如下:
从图中可以看出来,删除第2条记录前后主要发生的变化:
- 被删记录的delete_mask变为1,next_record变为0,其存储空间并没有被移除。
- 被删记录的前一条记录的next_record指向后一条记录。
所以不管我们怎么对页中的记录做增删改操作,InnoDB始终会维护一条记录的单链表,保证链表中各个节点是按照主键由小到大的顺序连接起来的。
值得注意的是,当记录删除后,其存储空间并没有回收,当我们再次插入记录的时候,会直接复用原来被删除记录的存储空间,而没有申请新的存储空间。当数据页中存在多条被删除的记录时,这些记录会通过next_record组成一个垃圾链表,以备之后重用这部分存储空间。
小结
当我们插入记录的时候会按照指定的行格式存储到User Records部分,每当我们插入一条记录,都会从Free Space部分申请一个记录大小的空间划分到User Records部分;
当记录删除后,其存储空间并没有回收,当我们再次插入记录的时候,会直接复用原来被删除记录的存储空间;