从根儿上理解 MySQL - 页总结

页结构

因为 MySQL 的真实数据是存储在磁盘, 所以在读写数据是会涉及磁盘 IO, 为了更高效率的读取, MySQL 设计页结构, 每次交互以页为单位读取到内存. 页的大小通常为 16KBbash

一个数据页能够被大体划分为7个部分优化

File Header,表示页的一些通用信息,占固定的38字节。 Page Header,表示数据页专有的一些信息,占固定的56个字节。 Infimum + Supremum,两个虚拟的伪记录,分别表示页中的最小和最大记录,占固定的26个字节。 User Records:真实存储咱们插入的记录的部分,大小不固定。 Free Space:页中还没有使用的部分,大小不肯定。 Page Directory:页中的某些记录相对位置,也就是各个槽在页面中的地址偏移量,大小不固定,插入的记录越多,这个部分占用的空间越多。 File Trailer:用于检验页是否完整的部分,占用固定的8个字节。spa

image-20200129175558772

image-20200129175624645

上述的行格式即为 User Records的一部分设计

新生成的页并无 User Records, 当插入数据时, 会从 Free Space 申请, 若是 Free Space 的空间所有被替换掉, 则说明该页满了, 须要申请新的页3d

Page Directory (页目录)

image-20200129180219930

从 select * from table wher id = 1 讲起 比较笨的方式会从最小记录开始遍历往下找, 知道找到 next_record = 0指针

MySQL 的优化点在于新增了一个目录, 利用二分查找优化code

  1. 经过二分法肯定该记录所在的槽(实际是找到肯定的槽, 遍历该槽的上一个槽或者当前槽)
  2. 经过记录的next_record属性遍历该槽所在的组中的各个记录

image-20200129180437808

由于把16条记录的所有信息都画在一张图里太占地方,让人眼花缭乱的,因此只保留了用户记录头信息中的n_owned和next_record属性,也省略了各个记录之间的箭头,我没画不等于没有啊!如今看怎么从这个页目录中查找记录。由于各个槽表明的记录的主键值都是从小到大排序的,因此咱们可使用所谓的二分法来进行快速查找。5个槽的编号分别是:0、一、二、三、4,因此初始状况下最低的槽就是low=0,最高的槽就是high=4。比方说咱们想找主键值为6的记录,过程是这样的:

计算中间槽的位置:(0+4)/2=2,因此查看槽2对应记录的主键值为8,又由于8 > 6,因此设置high=2,low保持不变。

从新计算中间槽的位置:(0+2)/2=1,因此查看槽1对应的主键值为4,又由于4 < 6,因此设置low=1,high保持不变。

由于high - low的值为1,因此肯定主键值为6的记录在槽2对应的组中。此刻咱们须要找到槽2中主键值最小的那条记录,而后沿着单向链表遍历槽2中的记录。可是咱们前边又说过,每一个槽对应的记录都是该组中主键值最大的记录,这里槽2对应的记录是主键值为8的记录,怎么定位一个组中最小的记录呢?别忘了各个槽都是挨着的,咱们能够很轻易的拿到槽1对应的记录(主键值为4),该条记录的下一条记录就是槽2中主键值最小的记录,该记录的主键值为5。因此咱们能够从这条主键值为5的记录出发,遍历槽2中的各条记录,直到找到主键值为6的那条记录便可。因为一个组中包含的记录条数只能是1~8条,因此遍历一个组中的记录的代价是很小的。

因此在一个数据页中查找指定主键值的记录的过程分为两步:

经过二分法肯定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。

经过记录的next_record属性遍历该槽所在的组中的各个记录。
复制代码

行格式

行格式主要分为四种类型CompactRedundantDynamicCompressed. 主要理解 Compactcdn

Compact 行格式

image-20200129145838600

行格式主要分为记录的额外信息, 记录的真实数据blog

变长字段长度列表

变长字段长度列表存储的是变长类型的真实数据的占用字节数(逆序).排序

若是该表没有变长类型, 则无变长字段长度列表

变长字段长度列表只保存 NOT NULL列, 若是该列容许为 NULL, 则不会保存

举例: 字段 0 int 0, 字段1 vachar(10) NOT NULL 'A', 字段2 vachar(10) default NULL 'B', 字段3 vachar(10) NOT NULL 'AA'
变长字段长度列表: 02 01
可经过 变长字段反推字段的长度
复制代码

NULL 值列表

NULL 值列表只统计哪些字段容许为 NULL的值状态(逆序)

若是该表没有容许 NULL 列, 则无 NULL 值列表

MySQL规定NULL值列表必须用整数个字节的位表示,若是使用的二进制位个数不是整数个字节,则在字节的高位补0

二进制位的值为1时,表明该列的值为NULL 二进制位的值为0时,表明该列的值不为NULL

举例: 字段 0 vachar(10) NOT NULL 'A', 字段1 vachar(10) default NULL 'B', 字段2 vachar(10) default NULL NULL, 字段3 vachar(10) default NULL NULL
逆序后: 字段3 - 1, 字段2 - 1, 字段1 - 0
NULL 值列表: 00000110 
可经过 NOT NULL 字段反推哪些字段值为 NULL, 哪些字段值不为 NULL
复制代码

记录头

这一部分牵扯的内容较多

大体能够看一下删除标识(删除是并不是真正删除, 只是修改表示), 下一条记录位置(B+树特性)

image-20200129152146891

image-20200129152534179

除了这些详细信息, 还会有 MySQL 自动添加的隐藏列

实际上这几个列的真正名称实际上是:DB_ROW_ID, DB_TRX_ID, DB_ROLL_PTR, 为了美观才写成了row_id、transaction_id和roll_pointer

row_id: 有主键使用主键, 没有主键有惟一键使用惟一键, 没有则自动生成

transaction_id: 事务 ID

roll_pointer: 回滚指针

image-20200129152851500

行溢出

由于一个页占用 16KB, 16 * 1024 = 16384 字节, 而 varchar 最多能够占用65535, 不包括隐藏列和记录头信息

可是在咱们使用 varchar 时, 会设置变长字段长度列表和 NULL 值列表

对于 NOT NULL, 只能使用65533字节 (两个字节用来表示长度) 对于非 NOT NULL, 只用使用 65532 字节 (两个字节用来表示长度, 一个字节用来表示 NULL 标识)

image-20200129154058216

对于行溢出的状况, 真实的数据只用存储前 768 字节, 后面放的是溢出页地址
复制代码

行溢出的节点

综上所述, 咱们知道在数据的占用字节数超过某个阈值就会发生行溢出, 那么行溢出的计算方式分析以下

ps: 截图来源于MySQL 是怎样运行的:从根儿上理解 MySQL(小孩子 4919)

image-20200129154804902

其余

delete_mask

这个属性标识该条记录是否已删除

若是 MySQL 设计删除一条则执行磁盘删除, 会致使磁盘IO增长, MySQL 为了优化, 使用一个标识表示该记录已被删除, 若是有新的记录来, 会覆盖该条记录 原理: 删除数据时, 标识该记录已删除, 而且放入垃圾链表, 新的记录插入时, 先从垃圾链表中替换

若是想要优化空间, 则执行 optimize table 'name' 便可

参考文献

MySQL 是怎样运行的:从根儿上理解 MySQL(记录结构) MySQL 是怎样运行的:从根儿上理解 MySQL(数据页结构)

相关文章
相关标签/搜索