MySQL数据库Innodb储存引擎----储存页的结构
上一篇博客回顾:
1:数据库拥有众多的储存引擎,现在主要使用的是Inoodb,这个储存引擎有Compact,Redundant,Dynamic,Compressed四种行格式
2:Compact行格式的结构分为变长数据长度列表,NULL值列表,记录头信息,真是数据储存
3:变长数据长度列表储存的是变长数据类型数据的字节数逆顺序,空值列不储存,NULL值列表储存非主键和没有被NOT NULL 修饰的列,二进制位逆顺序进行储存。
4:记录头信息包括了偏移量,槽数量,本组数据量,是否被删除,数据类型,是不是B+树子节点等等信息。
5:真实数据会有三个三个虚拟列,ROW_ID(没有主键的时候自动生成),ROLL_POINTER,TRANSACTION_ID(事务管理ID)
5:行溢出数据的处理Compact行格式是使用最后记录下一页地址的方式,然而Redundant和Compressed是采用整页记录数据页地址的方式,后两者的Compressed采用压缩算法。
6:对于char相似类型的数据来说,如果我们采用可变的字符集进行操作也是会在可变长度数据列表里面进行储存的。
7:对于可变数据长度列表储存的占用字节为1或者2,NULL值用二进制位,记录头信息在Compact行格式占用5个字节,Redundant占用6字节。一页空间16kb。
页储存结构:
我们都知道的是数据库一个页的储存空间是16kb,那么这16kb的储存空间是怎么进行分配,数据在这个储存空间里是怎么样的一个格式呢?对于这些数据数据库都进行了什么操作?这就是我们今天需要进行学习的内容。
可能按照顺序来说的话不是太方便进行讲诉,而且看起来可能也不会效果太好,所以我们一点一点根据功能的划分进行区分。
User Records:
这个区域对我们插入的数据进行保存,需要说明的是原本这一块是不存在的,当我们插入数据的时候这块区域才会被划分出来。并且是从Free Space进行的划分。当我们的Free Space区域所有的空间都变成了UserRecords,那么这时候就是需要重新开辟一个储存页的时间了。
那么当我们把数据放在这个区域里面的时候,是不是就是说没有一点规则,随便进行摆放,其实想一想就知道了,当我们数据过于庞大的时候,我们随便摆放,查找会是多么的痛苦。所以接下来在我们知道数据保存在那个位置以后我们需要弄清楚的是数据在User Records里的情况。在这里我们假设插入了四条我们自己的记录:
两个虚拟的数据:我们可以看到下图所示,我们插入了四条记录的时候,但是在我们这个页中存在的是六条记录,也就是两条我们说的每个页中都会存在的虚拟记录:最大记录和最小记录。他们都存infumum_supremum里面,因为不是我们自己插入的记录所以是不在User_Record里面.最小记录默认在开始,最大记录在最末尾结束
接下来我们再根据这六条记录描述一些问题:
1:我们在每条数据的记录头里面提到的Record_Type标记的是这条数据的类型,当时我们说的是有0普通数据,1叶子节点数据,2最小数据,3最大数据。我们可以看到的是上边最大和 最小数据分别是3和2, 我们自己插入数据的记录头信息在Record_type这里都是0.
2:我们在记录头信息里面还可以看到的是delete_mask这个数据,表示的是数据是否被删除,0表示没有,1表示已经被删除,所以上边的数据都是0
3:heap_no我们讲过是标记该数据在页中的位置,我们可以看到插入数据分别是2,3,4,5。那么0和1去哪了,别着急,请看看最小记录和最大记录的该数据,是不是分别为0和1。我 们插入的数据都会从2开始计数,虚拟数据会占用默认的0和1的位置。
4:插入数据的排列是否就是数据插入的顺序,那显然是不可能的,你没想错,数据会根据大小进行排列,那么数据用什么进行大小的排列?显然就是主键进行比较。
5:next_record记录的就是相对于本条数据,下一条数据的地址偏移量,就是通过这条数据往下查找这么多字节就可以找到下一条数据,没错。他就是使用的链表进行链接的。如下图:
6:如果一条数据被删除,也就是它的delete_mask被标记为了1,那么这个会怎么进行改变?就是和链表一致,进行链接的切断就可以了。
7:我们在进行数据查找的时候就是这么一条接一条的进行查找么?从最小记录开始根据next_record查找?那必然是耗时的一个活,显然是不可能的,所以在就出现了分组这个概念。
分组:
我们可以看到的是六条数据分成了两组,首先是最小的虚拟数据独自一组,然后剩下的五个数据再分成一组。在这里需要知道的就是MySql数据库在每个页中进行数据分组的时候默认的 最小数据是第一组,它拥有一条数据,就是最小数据,不能在插入其他数据。最大数据是第二组,我们在进行数据插入的时候都是先插入最大数据组,当最大数据组满足的时候进行分裂,形 成普通的分组,然后再进来的数据又插入最大数据组,如此循环往复,完成数据的分组。
槽:
我们还可以看到的是在分组的图里面出现了两个奇奇怪怪的东西,槽,我们上一篇文章页诉说过这个玩意儿。每个分组数据的相对于页的地址偏移量就是一个槽数据,一个分组有一个 槽,槽存在的位置就是页信息的Page Directory。在这里我需要强调的是在记录头信息中有个地址偏移量next_record,这个偏移量是本条数据相对于下一条数据位置,然后槽中的偏移量是分 组最后一条数据相对于页的偏移量。
寻找:
有了分组以后,我们在进行数据查找的时候就是根据二分法确定对应数据所在的槽位置,然后在使用记录头信息的next_record一条条进行查找。
n_owned:
这个数据我们在记录头信息中一直看到,其实在这里就可以结束这个数据了。它表示的是该分组有多少条数据,存在于分组的最后一条信息中。我们可以看到的是每个分组的前面的数据 n_owned都是0,只有在最后一条数据上它才有值。
page_header
上边我们通过数据的方式介绍了User_Records,infumum_supremum,page_directory,Free spce这四块空间的使用情况,接下来需要进行解释的就是page_header,file_header,file tailer这三块空间。
首先说的就是page_header,这个地方储存的就是数据的一些信息:
PAGE_N_DIR_SLOTS |
2 字节 |
在页目录中的槽数量 |
PAGE_HEAP_TOP |
2 字节 |
第一个记录的地址 |
PAGE_N_HEAP |
2 字节 |
本页中的记录的数量(包括最小和最大记录以及标记为删除的记录) |
PAGE_FREE |
2 字节 |
指向可重用空间的地址(就是标记为删除的记录地址) |
PAGE_GARBAGE |
2 字节 |
已删除的字节数,行记录结构中delete_flag 为1的记录大小总数 |
PAGE_LAST_INSERT |
2 字节 |
最后插入记录的位置 |
PAGE_DIRECTION |
2 字节 |
最后插入的方向 |
PAGE_N_DIRECTION |
2 字节 |
一个方向连续插入的记录数量 |
PAGE_N_RECS |
2 字节 |
该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录) |
PAGE_MAX_TRX_ID |
8 字节 |
修改当前页的最大事务ID,该值仅在二级索引中定义 |
PAGE_LEVEL |
2 字节 |
当前页在索引树中的位置,高度 |
PAGE_INDEX_ID |
8 字节 |
索引ID,表示当前页属于哪个索引 |
PAGE_BTR |
10 字节 |
非叶节点所在段的segment header,仅在B+树的Root页定义 |
PAGE_LEVEL |
10 字节 |
B+树所在段的segment header,仅在B+树的Root页定义 |
在上边我们需要说的就是PAGE_DIRECTION和PAGE_N_RECS这两个数据第一个指的是最后插入的方向,相对于上一条数据来说,我们新插入的比他大,就在右边,反之则在左边,这就是方 向。当我们插入数据连续的都在右边或者是都在左边的时候就会记录下数量。当然如果改变方向的话这个数据会被清空从零开始计数。
File_Header:
上边讲的page_header就是对页储存记录的描述,那么这里的File_Header就是对页信息的描述:
名称 | 占用空间大小 | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM |
4 字节 |
页的校验和(checksum值) |
FIL_PAGE_OFFSET |
4 字节 |
页号 |
FIL_PAGE_PREV |
4 字节 |
上一个页的页号 |
FIL_PAGE_NEXT |
4 字节 |
下一个页的页号 |
FIL_PAGE_LSN |
8 字节 |
最后被修改的日志序列位置(英文名是:Log Sequence Number) |
FIL_PAGE_TYPE |
2 字节 |
该页的类型 |
FIL_PAGE_FILE_FLUSH_LSN |
8 字节 |
仅在系统表空间的一个页中定义,代表文件至少被更新到了该LSN值,独立表空间中都是0 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID |
4 字节 |
页属于哪个表空间 |
我们可以看到三个值,一个记录的是这个页的页号,上一个页,下一个页。针对上面我们讲的一个页中的数据是采用单向链表的形式进行连接,那么我们可以想到的是数据库中的页是采用的双向链表。
我们在上面还可以看到的是 FIL_PAGE_TYPE这个值,描述的是这个页的类型,显然我们数据库不可能就只有一种数据页,上面我们讲的储存真实数据页就是数据页。FIL_PAGE_INDEX,也就是我们提到的 B+树叶子节点。
名称 | 十六进制 | 描述 |
---|---|---|
FIL_PAGE_ALLOCATED |
0x0000 | 最新分配,还没使用 |
FIL_PAGE_UNDO_LOG |
0x0002 | Undo Log页 |
FIL_PAGE_INODE |
0x0003 | 段信息的节点 |
FIL_PAGE_IBUUF_FRE_LIST |
0x0004 | Insert Buffer空闲列表 |
FIL_PAGE_IBUF_BITMAP |
0x0005 | Insert Buffer位图 |
FIL_PAGE_TYPE_SYS |
0x0006 | 系统页 |
FIL_PAGE_TYPE_TRX_SYS |
0x0007 | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR |
0x0008 | File Space Header |
FIL_PAGE_TYPE_XDES |
0x0009 | 扩展描述页 |
FIL_PAGE_TYPE_BLOB |
0x000A | BLOB页 |
FIL_PAGE_INDEX |
0x45BF | B+树的子节点 |
File Trailer
这玩意需要和上面File header中的FIL_PAGE_SPACE_OR_CHKSUM
这个属性系统校验和一起说。我们都知道的是页是一块16kb的储存空间,不管是内存刷新到数据库取用都是一次操作16kb。那么如果在中途产生停电等不可抗拒的因素,这时候这里就起作用了。File Header位于页的开始,它会计算一个校验和,这个校验和你可以这么理解,当我们需要一个很复杂的字符串的时候,往往会将它按照一定的算法进行计算出一个整数值,当和其它字符串进行比较的时候就用这个值。所以校验和也是这个道理,File Trailer是位于页尾部的,他也会储存一个校验和。如果数据不完整,那么两个校验和不可能一致,那么就可以判定这个数据页是损坏的。