MySQL存储引擎InnoDB之数据页

MySQL存储引擎InnoDB之数据页,第1张

MySQL存储引擎InnoDB之数据

文章目录
  • 1、存储引擎作用和结构?
  • 2、InnoDB数据页(默认16KB)结构?
  • 3、File Header文件头部结构(页的第一部分)?
  • 4、Page Header页头部结构(页的第二部分)?
  • 5、User Record记录头信息结构和属性变化(页的第四部分)?
    • (1)记录头信息结构
    • (2)属性变化
  • 6、Page Directory页目录作用(页的第六部分)?
    • (1)Page Directory构造过程:
    • (2)分组规定
    • (3)分组过程
    • (4)查找过程
  • 7、File Tailer文件尾部结构(页的第七部分)?
  • 8、页总结

1、存储引擎作用和结构?
InnoDB存储引擎作用:
(1)MySQL服务器上负责对表中数据的读取和写入工作的部分;
(2)MySQL目前默认存储引擎为InnoDB,读取数据为从磁盘读取到内存,写入和修改则是从内存刷新到磁盘;
(3)InnoDB是将数据分为若干页,以页作为磁盘和内存的基本交互单位,InnoDB每页大小为16KB,
    即一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
InnoDB存储引擎结构:
(1)磁盘和内存的交互为页,是MySQL管理存储空间的基本单位;
(2)执行行格式和修改行格式的语法为:
    创建:CREATE TABLE 表名 {列的信息} ROW_FORMAT=行格式名称
    修改:ALTER TABLE 表名 ROW_FORMAT=行格式名称;
(3)InnoDB 目前定义了4种行格式:Compact行格式、Redundant行格式、Dynamic行格式和Compressed行格式;
(4)一个页一般是16KB,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中即行溢出。
PS:Compact行格式结构:记录的额外信息和记录的真实信息。
      额外信息包括:变长字段长度列表、Null值列表和记录头信息;
      记录的真实信息为:各列的真实列值,其中null值的列会被放进额外信息中的Null值列表,避免冗余。
    Redundant行格式结构:MySQL5.0以前的版本,记录的额外信息和记录的真实信息。
      额外信息为:字段长度偏移列表和记录头信息,没有Null值列表;
      记录的真实信息为:各列的真实列值,其中null值的列也会存在真实数据中,比较粗暴。
    Dynamic和Compressed行格式结构: 
    	MySQL版本是5.7,它的默认行格式就是Dynamic,行溢出 数据时有点儿分歧,它们不会在记录的真实数据处存储字段真实数据的前
    	768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页
      面的地址。
    Compact与Redundant行格式区别:
    	Compact行格式的开头是变长字段长度列表,而Redundant行格式的开头是字段长度偏移列表,与变长字段长度列表有两处不同:
    	(1)没有了变长两个字,意味着 Redundant行格式会把该条记录中所有列(包括隐藏列)的长度信息都按照逆序存储到字段长度
    	偏移列表;
    	(2)多了个偏移两个字,这意味着计算列值长度的方式不像Compact行格式那么直观,它是采用两个相邻数值的差值来计算各个列值
    	的长度。

Compact行格式:

Redundant行格式:

Dynamic和Compressed行格式:
        Dynamic和Compressed行格式与 Compact行格式类似 ,仅在处理行溢出数据时不一样,它们不会在记录的真实数据处存储字符串的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。
PS:Compressed 行格式会采用压缩算法对页面进行压缩。

2、InnoDB数据页(默认16KB)结构?

      InnoDB数据页共分7个部分:File Header(文件头部 38字节)、Page Header(页面头部 56字节)、Infimum+Supremum(最小记录和最大记录 26字节)、User Records(用户记录 不确定)、Free Space(空闲空间 不确定)、Page Directory(页面目录 不确定)和
File Tailer(文件尾部 8字节)。

 (1)File Header:数据页的通用信息;
 (2)Page Header:数据页专有信息;
 (3)Infimum+Supremum:两个虚拟行记录;
 (4)User Records:实际存储的行记录;
 (5)Free Space:数据页中未使用到的空间;
 (6)Page Directory:页目录即数据页中某些记录的相对位置;
 (7)File Tailer:校验数据页是否完整。

InnoDB数据页结构:

3、File Header文件头部结构(页的第一部分)?

针对各种页都通用的一些信息,如页编号、上一页、下一页等等,其内部结构如下:

(1)FIL_PAGE_SPACE_OR_CHKSUM:占用4字节,页的校验和(checksum值)
    某种算法来计算一个比较短的值来代表这个很长的字节串即为校验和;
(2)FIL_PAGE_OFFSET:占用4字节,页号
    每一个页都有一个单独的页号,InnoDB通过页号来可以唯一定位一个页;
(3)FIL_PAGE_PREV:占用4字节,上一个页的页号
(4)FIL_PAGE_NEXT:占用4字节,下一个页的页号
(5)FIL_PAGE_LSN:占用8字节,页面被最后修改时对应的日志序列位置(Log Sequence Number)
(6)FIL_PAGE_TYPE:占用2字节,该页的类型
    InnoDB为了不同的目的而把页分为不同的类型,具体如下:|类型名称|十六进制|描述| |:--:|:--:|:--:|
  (1)| FIL_PAGE_TYPE_ALLOCATED |0x0000|最新分配,还没使用|
  (2)| FIL_PAGE_UNDO_LOG |0x0002|Undo日志页|
  (3)| FIL_PAGE_INODE |0x0003|段信息节点|
  (4)| FIL_PAGE_IBUF_FREE_LIST |0x0004|Insert Buffer空闲列表|
  (5)| FIL_PAGE_IBUF_BITMAP |0x0005|Insert Buffer位图|
  (6)| FIL_PAGE_TYPE_SYS |0x0006|系统页|
  (7)| FIL_PAGE_TYPE_TRX_SYS |0x0007|事务系统数据|
  (8)| FIL_PAGE_TYPE_FSP_HDR |0x0008|表空间头部信息|
  (9)| FIL_PAGE_TYPE_XDES |0x0009|扩展描述页|
  (10)| FIL_PAGE_TYPE_BLOB |0x000A|BLOB页|
  (11)| FIL_PAGE_INDEX |0x45BF|索引页即数据页
(7)FIL_PAGE_FILE_FLUSH_LSN:占用8字节,仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值
(8)FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID:占用4字节,页属于哪个表空间

PS:
      (1)并不是所有类型的页都有上一个页(FIL_PAGE_PREV)和下一个页(FIL_PAGE_NEXT)的属性,但数据页是有这两个数据属性的,如一张表数据量过大,16kb存不下,就会分成很多个页,此时就这两个属性作用就发挥作用;
      (2)所有类型的页的File Header都是通用的。

4、Page Header页头部结构(页的第二部分)?

记录一个数据页中存储的数据记录状态信息占用固定56字节,专门存储各种状态信息,其内部结构如下:

(1)PAGE_N_DIR_SLOTS:占用2字节,在页目录中的槽数量
(2)PAGE_HEAP_TOP:占用2字节,还未使用的空间最小地址,从该地址之后就是Free Space
(3)PAGE_N_HEAP:占用2字节,本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)
(4)PAGE_FREE:占用2字节,第一个已经标记为删除的记录地址(各个已删除的记录通过 next_record 也会组成一个单链表,这个单链表
中的记录可以被重新利用)
(5)PAGE_GARBAGE:占用2字节,已删除记录占用的字节数
(6)PAGE_LAST_INSERT:占用2字节,最后插入记录的位置
(7)PAGE_DIRECTION:占用2字节,记录插入的方向即新插入的记录比上一记录主键大,则当前记录插入的方向为右边,反之左边
(8)PAGE_N_DIRECTION:占用2字节,一个方向连续插入的记录数量即插入记录连续同一方向的数量,某一次改变直接会被清空重新统计
(9)PAGE_N_RECS:占用2字节,该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)
(10)PAGE_MAX_TRX_ID:占用8字节,修改当前页的最大事务ID,该值仅在二级索引中定义
(11)PAGE_LEVEL:占用2字节,当前页在B+树中所处的层级
(12)PAGE_INDEX_ID:占用8字节,索引ID,表示当前页属于哪个索引
(13)PAGE_BTR_SEG_LEAF:占用10字节,B+树叶子段的头部信息,仅在B+树的Root页定义
(14)PAGE_BTR_SEG_TOP:占用10字节,B+树非叶子段的头部信息,仅在B+树的Root页定义
5、User Record记录头信息结构和属性变化(页的第四部分)? (1)记录头信息结构
(1)预留位1、预留位2;
(2)delete_mask(标记是否删除);
(3)min_rec_mask(B+树种每层非叶子节点最小记录会添加该标记);
(4)n_owned(当前记录拥有的记录数);
(5)heap_no(当前记录在记录堆中的相对位置);
(6)record_type(当前记录类型 0普通记录 1B+树中非叶子节点 2最小记录 3最大记录);
(7)next_record(下一条记录的相对位置);

(2)属性变化

(1)delete_mask:当前记录删除标记,0表示未删除,1表示删除,占用1个二进制位;
注意:当记录被删除时,会对记录进行删除标记为1,但其仍然存储在原来的磁盘位置,如果移除会导致其他记录重新排列消耗性能, 故将所有删除记录组成一个垃圾链表,这些链表中的记录占用的空间称为可重用空间
(2)min_rec_mask:B+树的每层非叶子节点最小记录会添加该标记;
(3)n_owned:当前记录拥有的记录数;
(4)heap_no:在当前数据页中的位置,记录从小到大排列,一条完整的数据记录是比较主键大小;
注意:每个数据页中InnoDB会自动添加最小记录和最大记录,存储在Infimum+Supremum部分,
最小记录heap_no为User Record中主键还小的值,最大记录heap_no则是User Record中主键还大的值;
如:User Record记录中有1,2,3,4主键记录,那么最小记录heap_no为0,最大记录heap_no为5
(5)record_type:当前记录的类型,0为普通记录即用户插入数据,1为B+树中非叶子节点即索引,2为InnoDB自动添加的最小记录,3为InnoDB自动添加的最大记录;
(6)next_record:用于数据之间的链接即叶子节点是从小到大的链表,表示当前记录的真实数据到下一条真实数据的地址偏移量(主键大小排列)。

6、Page Directory页目录作用(页的第六部分)?
(1)将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组;
(2)每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,即该组内共有几条记录;
(3)将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方即Page Directory,页目录中的这些地址偏移量
被称为槽(英文名:Slot),即页面目录就是由槽组成的。
(1)Page Directory构造过程:

场景1:假定用户插入1、2、3、4四条记录。
    现在页目录部分中有两个槽,也就意味着我们的记录被分成了两个组,槽1中的值是112,代表最大记录的地址偏移量(就是从页面的0字节开始数,数112个字节); 槽0中的值是99,代表最小记录的地址偏移量。
    注意最小和最大记录的头信息中的n_owned属性
(1)最小记录的n_owned值为1,这就代表着以最小记录结尾的这个分组中只有1条记录,也就是最小记录本身。
(2)最大记录的n_owned值为5,这就代表着以最大记录结尾的这个分组中只有5条记录,包括最大记录本身还有我们自己插入的4条记录。

(2)分组规定

    对于最小记录所在的分组只能有1条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是4~8条之间。

(3)分组过程

(1)初始情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组。
(2)之后每插入一条记录,都会从页目录 中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应的记录的n_owned 值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个。
(3)在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录 中新增一个 来记录这个新增分组中最大的那条记录的偏移量。

(4)查找过程

场景2:单个数据页中查找指定数据记录主键6,假定用户插入16条记录,包括最大记录和最小记录,共计18条记录。

5个槽的编号分别是: 0 、 1 、 2 、 3 、 4 ,所以初始情况下最低的槽就是 low=0 ,最高的槽就是high=4 。
查找过程如下:
(1)计算中间槽的位置: (0+4)/2=2,所以查看槽2 对应记录的主键值为8 ,又因为8 > 6 ,所以设置high=2,low 保持不变。
(2)重新计算中间槽的位置: (0+2)/2=1,所以查看 槽1 对应的主键值为 4 ,又因为 4 < 6 ,所以设置low=1,high 保持不变。
(3)因为 high - low 的值为1,所以确定主键值为 5 的记录在槽2 对应的组中。此刻需要找到槽2中主键值最小的那条记录,然后沿着单向链表遍历 槽2 中的记录。每个槽对应的记录都是该组中主键值最大的记录,这里槽2 对应的记录是主键值为 8 的记录,怎么定位一个组中最小的记录呢?别忘了各个槽都是挨着的,我们可以很轻易的拿到 槽1 对应的记录(主键值为 4 ),该条记录的下一条记录就是 槽2 中主键值最小的记录,该记录的主键值为 5 。所以我们可以从这条主键值为 5 的记录出发,遍历 槽 2 中的各条记录,直到找到主键值为 6 的那条记录即可。由于一个组中包含的记录条数只能是1~8条,所以遍历一个组中的记录的代价是很小的。

总结查找过程:
(1)通过二分法确定该记录所在的槽,并找到该槽中主键值最小的那条记录。
(2)通过记录的 next_record 属性遍历该槽所在的组中的各个记录。

7、File Tailer文件尾部结构(页的第七部分)?

InnoDB存储引擎会把数据存储到磁盘上,需要以页为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,
那么在修改后的某个时间需要把数据同步到磁盘中。但是在同步了一半的时候发生意外了(如断电、宕机等)咋办,
为了检测一个页是否完整(也就是在同步的时候有没有发生只同步一半的尴尬情况),InnoDB设计者在每个页的尾部都加了一个File Trailer部分,
这个部分由8个字节组成,可以分成2个小部分:

(1)前4个字节代表页的校验和
    这个部分是和File Header中的校验和相对应的。每当一个页面在内存中被修改,在同步之前就要把它的校验和算出来,因为
    File Header在页面的前边,所以校验和会被首先同步到磁盘。当完全写完时,校验和也会被写到页的尾部,如果完全同步成功,
    则页的首部和尾部的校验和应该是一致的。如果写了一半儿断电了,那么在File Header中的校验和就代表着已经修改过的页,
    而在File Tailer中的校验和代表着原先的页,二者不同则意味着同步中间出了错。
(2)后4个字节代表页面被最后修改时对应的日志序列位置(LSN)
    这个部分也是为了校验页的完整性的。

PS:所有类型的页的File Tailer都是通用的。

8、页总结
(1)InnoDB为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做 数据页 。 
(2)一个数据页可以被大致划分为7个部分如下:
    File Header:表示页的一些通用信息,占固定的38字节。
    Page Header:表示数据页专有的一些信息,占固定的56个字节。
    Infimum + Supremum:两个虚拟的伪记录,分别表示页中的最小和最大记录,占固定的26个字节。
    User Records:真实存储我们插入的记录的部分,大小不固定。
    Free Space:页中尚未使用的部分,大小不确定。
    Page Directory:页中的某些记录相对位置,也就是各个槽在页面中的地址偏移量,大小不固定,插入的记录越多,这个部分占用的
    空间越多。
    File Trailer:用于检验页是否完整的部分,占用固定的8个字节。
(3)每个记录的头信息中都有一个 next_record 属性,从而使页中的所有记录串联成一个单链表 。
(4)InnoDB 会为把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个槽,存放在Page Directory中,
    所以在一个页中根据主键查找记录是非常快的,分为两步:
        通过二分法确定该记录所在的槽。
        通过记录的next_record属性遍历该槽所在的组中的各个记录。
(5)每个数据页的 File Header 部分都有上一个和下一个页的编号,所以所有的数据页会组成一个 双链表 。 
(6)为保证从内存中同步到磁盘的页的完整性,在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的 LSN 值,
    如果首部和尾部的校验和和 LSN 值校验不成功的话,就说明同步过程出现了问题。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5637026.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存