InnoDB数据库存储结构

前言

本博客主要是对《MySQL是怎样运行的》一书的内容进行整理

前三章内容

MySQL一定要注意字符集的格式和比较规则,否则在传入中文或者其他内容时可能会出现异常

第四章 InnoDB记录存储结构

Innodb是一个将表中数据存储在磁盘上的存储引擎,但是真正处理数据的过程则是发生在内存中,因此需要把磁盘中的数据加载到内存中,处理完毕后需要把数据传回给磁盘。

为了避免一条条读取数据,InnoDB采用以页为单位从磁盘获取数据的方式。InnoDB中页大小通常为16KB。

延伸:磁盘与内存的交互有很大的学问,在实际处理时必须要斟酌。例如,以spark为例。spark里有两种处理数据的结构,一种是以行为单位记录数据的RDD,另一种则是以列为单位记录数据的dataframe。因为列的数据不连续,不在一个页上,所以经常会出现更新dataframe数据非常慢的情况。

行记录格式

一条行记录(COMPACT)的格式如下:

可以看出里面包含了记录的额外信息和记录的真实数据,下面分别进行分析

记录的额外信息

注意:一下这几个额外信息列每个都需要用整数个字节表示,如果信息不足整数个字节的话会通过填0的方式进行补充。

1、变长字段长度列表

MySQL支持varchar、text、blob等多种数据长度不确定的格式,因此为了记录长度方便读取,MySQL将这些变长字段的实际长度也进行了存取。

在变长字段长度列表中,各变长字段的真实数据占用的字节数按照列的顺序逆序存放(具体原因之后再说)

这里字节数使用一个字节表示还是两个字节表示不确定,需按照一定规则进行计算

2、NULL值列表

一条记录中的某些列可能存储NULL值,如果把这些NULL值都放到记录的真实数据中会很占地方,所以COMPACT格式把值为NULL的列统一管理,存储到列表中。具体流程如下:

(1)统计哪些列支持存储NULL

(2)每个可以存储NULL的列对应一个二进制位,逆序排列

3、记录头信息

这里暂时忽略,存储了一堆暂时看不懂的信息

记录的真实数据

记录的真实数据除了实际的列外,通常还有MySQL自己给行数据添加的隐藏列,如下所示:

列名是否必须描述
row_id行ID,唯一标识一条数据
trx_id事务ID(MVCC版本链可能用到)
roll_pointer回滚指针(undo log或者mvcc会用到)

InnoDB主键生成策略:优先使用用户自定义的主键作为主键;如果用户没有定义主键,则选取一个不允许存储NULL值的unique键作为主键;如果连UNIQUE键都没有,就默认添加一个名为row_id的隐藏列作为主键。

除了COMPACT还有REDUNDANT和DYNAMIC、COMPRESSED行格式,这里不再赘述,暂时忽略

第五章 InnoDB数据页结构

页是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。这一节主要介绍存放记录的索引页


插入数据的流程

每当插入一条记录时,都会从Free Space部分申请一个记录大小的空间,并将这部分空间划分给User Records部分。如果Free Space部分的空间不够,就表示这个页已经用完了,如果有新纪录插入,需要使用新的页了。

这里每一个行记录对应的记录头信息很关键,下面举例进行说明

image_1c9qs0j281knc16hc1hqsgj01v0o2c.png-82.8kB

* deleted_flag:这个属性用来标记当前记录是否被删除,占用1比特。值为0时表示记录没有被删除,值为1时表示记录被删除了。

为什么使用标记而不是直接删除记录呢?

答:在移除删除记录后还需要再磁盘上重新排列其他的记录,这会导致性能消耗,只打一个标记可以避免这个问题;

所有被删除的记录形成一个垃圾链表,就记录在这个链表中占用的空间称为可重用空间。之后若有新纪录插入到表中,它们就可能覆盖掉被删除的这些记录占用的存储空间。

* min_rec_flag:B+树每层非叶子节点中的最小的目录项记录都会添加上该标记。

* n_owned 暂时忽略

* heap_no:记录一条条紧密排列,形成的结构被称之为堆(heap)。为了方便管理这个堆,他们把一条记录在堆中的相对位置称之为heap_no。在页面前边的heap_no较小, 后面的较大。每新申请一条记录的存储空间时,对应的heap_no都会增加1。值得注意的是,没有heap_no为0和1的记录。

基于设计原因,MySQL给每个页增加了两条记录,分别为Infimum记录(0,对应最小值)和Supremum记录(1,对应最大值)。记录的大小是通过比较主键的大小来实现。

无论插入多少条记录,都规定,任何记录都比Infimum大,比Supremum小。

* record_type:这个属性表示当前记录的类型,一共有4种类型的记录,其中0表示普通记录,1表示B+树非叶节点的目录项记录(与索引有关),2表示infimum记录,3表示supremum记录。

* next_record:表示从当前记录的真实数据到下一套记录的真实数据的距离,本质上帮助记录形成了一个链表。

值得注意的是,下一条记录指的并不是插入顺序中的下一条记录,而是按照主键值由小到大的顺序排列的下一条记录。(始于Infimum,终于supremum)

特别值得注意的是,next_record指向的地方不是每条记录开始的地方,而是每条记录真实数据开始的地方

示意图:

image_1cot1r96210ph1jng1td41ouj85c13.png-120.5kB

原因:指向真实数据开始地方而不是记录开头位置保证了向左读取能读取到位置靠前的字段对应的字段长度信息,可能可以提高高速缓存的命中率。

页目录

已知,记录在页中按照主键值由小到大的顺序串联成一个单向链表。那么,如何根据主键值查找野种的某条记录呢?遍历吗?

为了实现二分搜索,实现页目录,做法如下:

1、将所有正常的记录(包括Infimum和Supremum记录,但不包括删除的记录)划分为几个组

2、每个组的最后一条记录(即最大的那条记录)被特别标记,其对应的记录头信息中的n_owned属性表示该组内有几条记录。

3、将每个组中最后一条记录在页面中的地址偏移量(就是该记录的真实数据和页面中第0个字节之间的距离)单独提取出来,按顺序存储到靠近页尾部的地方。这个地方就是page directory,这些地址偏移量称为槽(slot),每个槽占用两字节。

槽划分的规则:

infimum记录所在的分组只能有1条记录,supremum记录所在的分组拥有的记录条数只能在1-8之间,剩下的分组的记录条数在4-8之间。

image_1d6g64af2sgj1816ktl1q22dehp.png-189.1kB

 

槽空间是连续的,可以通过类似数组地址偏移量的方式快速找到特定的槽。

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

1、通过二分法确定该记录所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录(从前一个槽对应值,即前一个槽的最大值的next_record起)

2、通过记录的next_record属性遍历该槽所在分组,寻找记录

File Header(文件头部)

作用:记录与页相关的通用信息,对实现很多功能有较大帮助。

file header存储了一个页所在的表空间、页号、前后页的页号等信息。

通过前后页页号,这些页构成了一个双向链表,通过页号可以映射到表的起始地址,帮助用户快速查找对应信息。

File Trailer(文件尾部)

作用:用于检验刷新是否成功

内存与磁盘之间的交互较为缓慢,如果某页数据在内存中修改了,但是在刷新到磁盘的过程中突然断电,只刷新了一部分,应该如何识别?

File Trailer用于实现这一功能。

* 前4个字节代表页的校验和。这个部分与File Header中的校验和相对应。每当一个页面在内存中发生修改时,在撒胡新签要把页面的校验和算出来。因为file header在前面,所以file header的校验和会被首先刷新到磁盘,当完全写完时,页首和页尾的校验和应该一致。如果不一致,说明刷新出现问题。

* 后4个字节代表页面被最后修改时对应的LSN的后4字节,正常情况应该与header里的FIL_PAGE_LSN的后4个字节一致。不一致说明刷新有问题。

参考资料   

1、MySQL是怎样运行的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值