MySQL数据库学习之跟着掘金手册学-个人笔记与总结[4]-InnoDB数据页结构

11 篇文章 0 订阅

一、对上一章的复习摘要

  • 页是MySQL中磁盘和内存交互的基本单位,也是MySQL中管理存储空间的基本单位。
  • 指定和修改行格式的语法如下:
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称
  • InnoDB目前定义了4种行格式:Compact(紧凑的)行格式、Redundant(冗余的)行格式、Dynamic和Compressed行格式(这两个不会在记录的真实数据处存储字符串的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址,Compressed行格式会采用压缩算法对页面进行压缩。)
  • 一个页一般是16KB,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出。

二、把我能想到的写下来

       上一节学习的是行格式,我们每次插入一个记录就是一行,在数据库中存储的格式。
       接下来学习页格式,针对不同类型的数据,会存储在不同的页中,比如专门存放数据的叫数据页,也叫索引页。

(改:针对不同目的,设计了不同类型的页。)

InnoDB 数据页主要由以下7个存储空间组成:
(1)File Header 文件头部
(2)Page Header 页头部
(3)Free Space 空白空间
(4)User Recorder 用户记录
(5)最小和最大记录
(6)?
(7)?

(补: InnoDB 数据页的存储空间分为7个部分:
(1)File Header 文件头部 38字节 页的通用信息
(2)Page Header 页面头部 56字节 数据页转有的一些信息
(3)Infimum Supremum 最小记录和最大记录 26字节 2个虚拟的行记录
(4)User Records 用户记录 不确定 实际存储的行记录内容
(5)Free Space 空闲空间 不确定 页中尚未使用的空间
(6)Page Directory 页面记录 不确定 页中某些记录的相对位置
(7)File Trailer 文件尾部 8字节 校验是否完整

       要插入记录时,会在Free Space(空闲空间)中申请一块区域(一条记录的大小),将空间划给User Recorder ,并将记录存入。不是按照插入的顺序的,是依据主键大小,从小到大形成单链表,之前行格式中有“记录头信息”(真实的信息那部分),在“记录头信息”中有以下属性:
next ???、delete_???、type???

(改:
next_record:从当前记录的真实数据到下一条记录的真实数据的地址偏移量。
delete_mask
补:
       record_type:当前记录的类型:0-普通记录,1-B+树非叶节点记录,2-最小记录,3-最大记录。
       min_rec_mask:B+树的每层非叶子节点的最小记录标记。
heap_no:当前记录在本页中的位置
       会自动给每个页加两个记录:(因为这两个记录不是我们自己加的,所以叫“伪记录”或“虚拟记录”)
       最小 Infimum,最大 Supremum 都是5字节的记录头信息和8字节大小的固定部分组成的:
在这里插入图片描述

        就相当于数据结构的链表中的next域,会指向比它的主键值大的下一条记录。形成的单链表会从最小记录–> a --> b…–>最大记录。
       如果要删除某条记录,不会删掉该节点,而是在delete_???那个属性做标记,置为1则是删除,0是未删除,然后把删除的记录形成删除链表,那些空间是可以复用的。

(改:delete_mask)

       在整个页中,还有“槽”的概念,记录是分组的,分组的规则是这样的:
       最小记录和最大记录先分别占个组,这样先有两个组了。
       接下来按照主键的大小,从小到大,会把记录插在比它大而且相差最小的那个组里,每个组中最大的记录是“槽”,相当于“槽”指向它自己表示的那个组的主键最大的记录,每组最多有8个记录。把我理解的“槽”画了出来( 灵 魂画手在线画图 ):
在这里插入图片描述
(改:我画的哪里不太对的样子… …实现单链表的是额外的记录信息里的记录头信息的一个属性 next_record 。)

(槽到底是个神马?一条条记录吗?槽是否有编号?槽指向的还需要指向下一个大的记录吗?)

(答:槽是每个组里主键值最大的那条记录的地址偏移量,会被单独提取出来按顺序放在页的 Page Directory页目录中,页目录里都是槽。
槽没有编号,被按从小到大的顺序放置。
需要,不影响。)

执行查找的SQL语句时,就是先通过槽定位到组,然后 ???

(见 三)

Page Header用于记录当前页的信息,比如页的类型?(索引页、B+数每层非叶子页 ??? )页中有多少条记录?有多少条被删除的记录?

(改:
在页目录中的槽数量,
还未使用的空间最小地址,
本页中的记录的数量(包括最小和最大记录以及标记为删除的记录),
第一个已经标记为删除的记录地址,
已删除记录占用的字节数,
最后插入记录的位置,
记录插入的方向,
一个方向连续插入的记录数量,
该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录),
修改当前页的最大事务ID(该值仅在二级索引中定义,当前页在B+树中所处的层级),
索引ID(表示当前页属于哪个索引),
B+树叶子段的头部信息(仅在B+树的Root页定义),
B+树非叶子段的头部信息(仅在B+树的Root页定义)

File Header用于记录文件所有页的类型?多少页? ???

(改:
FIL_PAGE_SPACE_OR_CHKSUM 页的校验和(checksum值),
FIL_PAGE_OFFSET 页号,每一个页都有一个单独的页号,可以通过页号单独定位某一页 ,
FIL_PAGE_PREV 上一个页的页号,
FIL_PAGE_NEXT 下一个页的页号,
FIL_PAGE_LSN 页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number),
FIL_PAGE_TYPE 该页的类型:最新分配,还没使用; Undo日志页; 段信息节点; Insert Buffer空闲列表; Insert Buffer位图; 系统页 ;事务系统数据 ;表空间头部信息 ;扩展描述页 ;溢出页 ;索引页/数据页
FIL_PAGE_FILE_FLUSH_LSN 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值,
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 页属于哪个表空间。

三、补充

  • 记录与槽:
    在这里插入图片描述
    在这里插入图片描述

  • 关于==“槽”==:
    1.将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。
    2.每个组的最后一条记录(也就是组内主键值最大的那条)的头信息中的 n_owned 属性表示该组内共有多少条记录。
    3.把 每个组的最后一条记录的地址偏移量(“槽”)单独提取出来,按顺序存储到靠近页尾部的部分,这个地方就是 Page Directory(页面目录)。 Page Directory 页面目录就是由槽组成的。
    规定:
    对于最小记录所在的分组只能有 1 条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间。
    分组规则:
    (1)初始数据页里只有最小记录和最大记录,分属两个分组。
    (2)之后每插入一条记录,都从页目录中找到主键值比本记录大且差值最小的槽,然后把该槽对应的 n_owned 值+1,表示本组内又添加了一条记录,直到该组记录数=8 。
    (3)在一个组中的记录数等于8个后,再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录中新增一个槽来记录这个新增分组中最大的那条记录的偏移量。


  • 如何从这个页目录中查找记录
    因为各个槽代表的记录的主键值都是从小到大排序的,所以我们可以使用所谓的二分法来进行快速查找。
    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条,所以遍历一个组中的记录的代价是很小的。

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

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


  • Page Header中有两个重要属性:
PAGE_DIRECTION

假如新插入的一条记录的主键值比上一条记录的主键值大,我们说这条记录的插入方向是右边,反之则是左边。用来表示最后一条记录插入方向的状态就是PAGE_DIRECTION。

PAGE_N_DIRECTION

假设连续几次插入新记录的方向都是一致的,InnoDB会把沿着同一个方向插入记录的条数记下来,这个条数就用PAGE_N_DIRECTION这个状态表示。当然,如果最后一条记录的插入方向改变了的话,这个状态的值会被清零重新统计。


  • 在File Header 块的属性中有FIL_PAGE_PREVFIL_PAGE_NEXT

FIL_PAGE_PREVFIL_PAGE_NEXT就分别代表本页的上一个和下一个页的页号。这样通过建立一个双向链表把许许多多的页就都串联起来了,而无需这些页在物理上真正连着。需要注意的是,并不是所有类型的页都有上一个和下一个页的属性,不过数据页(也就是类型为FIL_PAGE_INDEX的页)是有这两个属性的,所以所有的数据页其实是一个双链表。


  • File Trailer
    我们知道InnoDB存储引擎会把数据存储到磁盘上,但是磁盘速度太慢,需要以页为单位把数据加载到内存中处理,如果该页中的数据在内存中被修改了,那么在修改后的某个时间需要把数据同步到磁盘中。但是在同步了一半的时候中断电了咋办,这不是莫名尴尬么?为了检测一个页是否完整(也就是在同步的时候有没有发生只同步一半的尴尬情况),设计InnoDB的大叔们在每个页的尾部都加了一个File Trailer部分,这个部分由8个字节组成,可以分成2个小部分:
    (1)前4个字节代表页的校验和
    这个部分是和File Header中的校验和相对应的。每当一个页面在内存中修改了,在同步之前就要把它的校验和算出来,因为File Header在页面的前边,所以校验和会被首先同步到磁盘,当完全写完时,校验和也会被写到页的尾部,如果完全同步成功,则页的首部和尾部的校验和应该是一致的。如果写了一半儿断电了,那么在File Header中的校验和就代表着已经修改过的页,而在File Trailer中的校验和代表着原先的页,二者不同则意味着同步中间出了错。

(2)后4个字节代表页面被最后修改时对应的日志序列位置(LSN)
这个部分也是为了校验页的完整性的。LSN且听以后分解。

File Trailer与File Header类似,都是所有类型的页通用的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值