数据管理宏观角度上的innodb理解

innodb中以记录为单位,与磁盘交互时,数据以页(一般为16kb)为交互基本单位进行io操作,一个独立表空间中的页可以达到2^32个(20多亿,每个页都有页号,由4个字节组成,因此可以存2^32个页),为了更好的管理这些页面,innodb中有一个区的概念,一个区默认占1M ,64个页(地址连续的,因此在B+树的叶子结点链表中读数据可以减小随机读),每256个区又被划分成一个组,InnoDB 对 B+树的叶子节点和非叶
子节点进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自 己独有的区。存放叶子节点的区的集合就算是一个段(segment),存放非叶子 节点的区的集合也算是一个段。也就是说一个索引会生成 2 个段,一个叶子节点段,一个非叶子节点段。
image.png

数据页结构

image.png

一个 InnoDB 数据页的存储空间大致被划分成了 7 个部分:
File Header 文件头部 38 字节 页的一些通用信息
Page Header 页面头部 56 字节 数据页专有的一些信息
Infimum + Supremum 最小记录和最大记录 26 字节 两个虚拟的行记录
User Records 用户记录 大小不确定 实际存储的行记录内容
Free Space 空闲空间 大小不确定 页中尚未使用的空间
Page Directory 页面目录 大小不确定 页中的某些记录的相对位置
File Trailer 文件尾部 8 字节 校验页是否完整

(如何校验页的写入完整性? 在写入时先写入fileheader 里面包含校验和,最后是 file trailer 里面也包含校验和,一致则表明完全写入)
当前记录被删除时,则会修改记录头信息中的 delete_mask 为 1,也就是说 被删除的记录还在页中,还在真实的磁盘上。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他记录在磁盘上重新排列需要性能消耗。所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为所谓的可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

innodb中四种行格式

InnoDB 存储引擎设计了 4 种不同类型的行格
式,分别是 Compact、Redundant、Dynamic 和 Compressed 行格式。
1.compact 紧凑
表中的某些列可能存储 NULL 值,如果把这些 NULL 值都放到记录的真实数 据中存储会很占地方,所以 Compact 行格式把这些值为 NULL 的列统一管理起来,存储到 NULL 值列表。每个允许存储 NULL 的列对应一个二进制位,二进制位的值为 1 时,代表该列的值为 NULL。二进制位的值为 0 时,代表该列的值不为 NULL。
2.Dynamic和Compressed
Dynamic 和 Compressed 行格式和Compact 行格式挺像,只不过在处理行溢出数据时有所不同。Compressed 行格式和 Dynamic 不同的一点是,Compressed 行格式会采用压缩算法对页面进行压
缩,以节省空间。
针对数据溢出的处理(当数据超过一页大小时):
Compact:
取该列的前 768 个字节的数据,然后把剩余的数据分 散存储在几个其他的页中,记录的真实数据处用 20 个字节存储指向这些页的地
址。这个过程也叫做行溢出,存储超出 768 字节的那些页面也被称为溢出页。
Dynamic 和 Compressed
不会在记录的真实数据处存储字段真实数 据的前 768 个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。

索引页格式

我们的记录按照主键从小到大的顺序形成了一个单链表,记录被删除,则从 这个链表上摘除。
image.png
在数据页中Page Directory主要用于解决链表查找问题
InnoDB 对单链表查找的改进是,为页中的记录再制作了一个目录,他们的制作过程是这
样的:
1、将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)
划分为几个组。2、每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的
n_owned 属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
3、将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近
页的尾部的地方,这个地方就是所谓的 Page Directory,也就是页目录页面目录
中的这些地址偏移量被称为槽(英文名:Slot),所以这个页面目录就是由槽组
成的。
image.png
这样,一个数据页中查找指定主键值的记录的过程分为两步:
通过二分法确定该记录所在的槽,并找到该槽所在分组中主键值最小的那条
记录。
通过记录的 next_record 属性遍历该槽所在的组中的各个记录。