mysql page_从根儿上理解 MySQL – 页总结

页结构

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

一个数据页可以被大致划分为7个部分

File Header,表示页的一些通用信息,占固定的38字节。

Page Header,表示数据页专有的一些信息,占固定的56个字节。

Infimum + Supremum,两个虚拟的伪记录,分别表示页中的最小和最大记录,占固定的26个字节。

User Records:真实存储我们插入的记录的部分,大小不固定。

Free Space:页中尚未使用的部分,大小不确定。

Page Directory:页中的某些记录相对位置,也就是各个槽在页面中的地址偏移量,大小不固定,插入的记录越多,这个部分占用的空间越多。

File Trailer:用于检验页是否完整的部分,占用固定的8个字节。

e351072c5c0672bde7f5b41a85019101.png

c087aa9ca4b740909e773aabae736ecb.png

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

新生成的页并没有 User Records, 当插入数据时, 会从 Free Space 申请, 如果 Free Space 的空间全部被替换掉, 则说明该页满了, 需要申请新的页

Page Directory (页目录)

3a5c003d29ce942c918b17f287badf95.png

从 select * from table wher id = 1 讲起

比较笨的方式会从最小记录开始遍历往下找, 知道找到 next_record = 0

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

通过二分法确定该记录所在的槽(实际是找到确定的槽, 遍历该槽的上一个槽或者当前槽)

通过记录的next_record属性遍历该槽所在的组中的各个记录

2bfd7db2669559373aaac088d1b55fd3.png

因为把16条记录的全部信息都画在一张图里太占地方,让人眼花缭乱的,所以只保留了用户记录头信息中的n_owned和next_record属性,也省略了各个记录之间的箭头,我没画不等于没有啊!现在看怎么从这个页目录中查找记录。因为各个槽代表的记录的主键值都是从小到大排序的,所以我们可以使用所谓的二分法来进行快速查找。5个槽的编号分别是:0、1、2、3、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属性遍历该槽所在的组中的各个记录。

行格式

行格式主要分为四种类型Compact、Redundant、Dynamic和Compressed. 主要理解 Compact

Compact 行格式

925405d6f2c04fe5e5e0338f96dd85e2.png

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

变长字段长度列表

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

如果该表没有变长类型, 则无变长字段长度列表

变长字段长度列表只保存 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+树特性)

33fd36831be1ea7b1df38f089e81141c.png

99a707b3b57931567493063064855767.png

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

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

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

transaction_id: 事务 ID

roll_pointer: 回滚指针

8eff827f1f331b13e6eed38a5ae71a3b.png

行溢出

因为一个页占用 16KB, 16 * 1024 = 16384 字节, 而 varchar 最多可以占用65535, 不包括隐藏列和记录头信息

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

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

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

3584a469a0df1a0e5192c46736405d85.png

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

行溢出的节点

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

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

17010e50dd1057701a004f3956a5c64e.png

其他

delete_mask

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

如果 MySQL 设计删除一条则执行磁盘删除, 会导致磁盘IO增加, MySQL 为了优化, 使用一个标识表示该记录已被删除, 如果有新的记录来, 会覆盖该条记录

原理: 删除数据时, 标识该记录已删除, 并且放入垃圾链表, 新的记录插入时, 先从垃圾链表中替换

如果想要优化空间, 则执行 optimize table 'name' 即可

参考文献

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值