「mysql是怎样运行的」第5章 盛放记录的大盒子---InnoDB数据页结构

news/2024/3/28 19:16:04/文章来源:https://blog.csdn.net/u014571143/article/details/129134404

「mysql是怎样运行的」第五章 盛放记录的大盒子—InnoDB数据页结构

文章目录

  • 「mysql是怎样运行的」第五章 盛放记录的大盒子---InnoDB数据页结构
    • @[toc]
    • 一、不同类型的页介绍
    • 二、数据页结构的快速浏览
    • 三、记录在页中的存储
        • 记录头信息的秘密
    • 四、Page Directory(页目录)
    • 五、Page Header(页面头部)
    • 六、File Header(文件头部)
    • 七、File Trailer(文件尾部)
    • 八、总结

一、不同类型的页介绍

前边我们简单提了一下的概念,它是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。InnoDB为了不同的目的而设计了许多种不同类型的页,比如存放表空间头部信息的页,存放Insert Buffer信息的页,存放INODE信息的页,存放undo日志信息的页等等等等。

我们聚焦的是那些存放我们表中记录的那种类型的页,官方称这种存放记录的页为索引(INDEX)页,鉴于我们还没有了解过索引是个什么东西,而这些表中的记录就是我们日常口中所称的数 据,所以目前还是叫这种存放记录的页为数据页吧。


二、数据页结构的快速浏览

数据页代表的这块16KB大小的存储空间可以被划分为多个部分,不同部分有不同的功能,各个部分如图所示:

image-20230114101013154

从图中可以看出,一个InnoDB数据页的存储空间大致被划分成了7个部分,有的部分占用的字节数是确定的,有的部分占用的字节数是不确定的。下边我们用表格的方式来大致描述一下这7个部分都存储 一些啥内容(快速的瞅一眼就行了,后边会详细唠叨的):

名称中文名占用空间大小简单描述
File Header文件头部38字节页的一些通用信息
Page Header页面头部56字节数据页专有的一些信息
Infimum + Supremum最小记录和最大记录26字节两个虚拟的行记录
User Records用户记录不确定实际存储的行记录内容
Free Space空闲空间不确定页中尚未使用的空间
Page Directory页面目录不确定页中的某些记录的相对位置
File Trailer文件尾部8字节校验页是否完整

三、记录在页中的存储

在页的7个组成部分中,我们自己存储的记录会按照我们指定的行格式存储到User Records部分。但是在一开始生成页的时候,其实并没有User Records这个部分,每当我们插入一条记录,都会从Free Space部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的 记录插入的话,就需要去申请新的页了,这个过程的图示如下:

image-20230114102929195

为了更好的管理在User Records中的这些记录,InnoDB可费了一番力气呢,在哪费力气了呢?不就是把记录按照指定的行格式一条一条摆在User Records部分么?其实这话还得从记录行格式的记录头信 息中说起。

记录头信息的秘密

image-20230114102036322

从图中可以看到,我们特意把记录头信息的5个字节的数据给标出来了,说明它很重要,我们再次先把这些记录头信息中各个属性的大体意思浏览一下(我们目前使用Compact行格式进行演示):

名称大小(单位:bit)描述
预留位11没有使用
预留位21没有使用
delete_mask1标记该记录是否被删除
min_rec_mask1B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned4表示当前记录拥有的记录数
heap_no13表示当前记录在记录堆的位置信息
record_type3表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录
next_record16表示下一条记录的相对位置

四、Page Directory(页目录)

现在我们了解了记录在页中按照主键值由小到大顺序串联成一个单链表,那如果我们想根据主键值查找页中的某条记录该咋办呢?比如说这样的查询语句:

SELECT * FROM page_demo WHERE c1 = 3;

最笨的办法:从Infimum记录(最小记录)开始,沿着链表一直往后找,总有一天会找到(或者找不到[摊手]),在找的时候还能投机取巧,因为链表中各个记录的值是按照从小到大顺序排列的,所以当链表的某个节点代表的记录的主键值大于你想要查找的主键值时,你就可以停止查找了,因为该节点后边的节点的主键值依次递增。

这个方法在页中存储的记录数量比较少的情况用起来也没啥问题,比方说现在我们的表里只有4条自己插入的记录,所以最多找4次就可以把所有记录都遍历一遍,但是如果一个页中存储了非常多的记 录,这么查找对性能来说还是有损耗的,所以我们说这种遍历查找这是一个笨办法。但是设计InnoDB的大叔们是什么人,他们能用这么笨的办法么,当然是要设计一种更6的查找方式喽,他们从书的目录中找到了灵感。

我们平常想从一本书中查找某个内容的时候,一般会先看目录,找到需要查找的内容对应的书的页码,然后到对应的页码查看内容。设计InnoDB的大叔们为我们的记录也制作了一个类似的目录,他们的制作过程是这样的:

  1. 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。
  2. 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
  3. 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这个地方就是所谓的Page Directory,也就是页目录(此时应该返回头看看页面各个部分的图)。页面目录 中的这些地址偏移量被称为槽(英文名:Slot),所以这个页面目录就是由槽组成的。

比方说现在的page_demo表中正常的记录共有6条,InnoDB会把它们分成两组,第一组中只有一个最小记录,第二组中是剩余的5条记录,看下边的示意图:

image-20230114103738685

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

99112这样的地址偏移量很不直观,我们用箭头指向的方式替代数字,这样更易于我们理解,所以修改后的示意图就是这样:

image-20230114103724078

是的,设计InnoDB的大叔们对每个分组中的记录条数是有规定的:对于最小记录所在的分组只能有 *1* 条记录,最大记录所在的分组拥有的记录条数只能在 *1~8* 条之间,剩下的分组中记录的条数范围只能在是 *4~8* 条之间。所以分组是按照下边的步骤进行的:

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

数据页中查找指定主键值的记录的过程

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

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

五、Page Header(页面头部)

设计InnoDB的大叔们为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫Page Header的部分,它是页结构的第二部分,这个部分占用固定的56个字节,专门存储各种状态信息,具体各个字节都是干嘛的看下表:

名称占用空间大小描述
PAGE_N_DIR_SLOTS2字节在页目录中的槽数量
PAGE_HEAP_TOP2字节还未使用的空间最小地址,也就是说从该地址之后就是Free Space
PAGE_N_HEAP2字节本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)
PAGE_FREE2字节第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链表,这个单链表中的记录可以被重新利用)
PAGE_GARBAGE2字节已删除记录占用的字节数
PAGE_LAST_INSERT2字节最后插入记录的位置
PAGE_DIRECTION2字节记录插入的方向
PAGE_N_DIRECTION2字节一个方向连续插入的记录数量
PAGE_N_RECS2字节该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)
PAGE_MAX_TRX_ID8字节修改当前页的最大事务ID,该值仅在二级索引中定义
PAGE_LEVEL2字节当前页在B+树中所处的层级
PAGE_INDEX_ID8字节索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF10字节B+树叶子段的头部信息,仅在B+树的Root页定义
PAGE_BTR_SEG_TOP10字节B+树非叶子段的头部信息,仅在B+树的Root页定义

在这里我们先唠叨一下PAGE_DIRECTIONPAGE_N_DIRECTION的意思:

  • PAGE_DIRECTION

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

  • PAGE_N_DIRECTION

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


六、File Header(文件头部)

上边唠叨的Page Header是专门针对数据页记录的各种状态信息,比方说页里头有多少个记录了呀,有多少个槽了呀。我们现在描述的File Header针对各种类型的页都通用,也就是说不同类型的页都会 以File Header作为第一个组成部分,它描述了一些针对各种页都通用的一些信息,比方说这个页的编号是多少,它的上一个页、下一个页是谁啦吧啦吧啦~ 这个部分占用固定的38个字节,是由下边这 些内容组成的:

名称占用空间大小描述
FIL_PAGE_SPACE_OR_CHKSUM4字节页的校验和(checksum值)
FIL_PAGE_OFFSET4字节页号
FIL_PAGE_PREV4字节上一个页的页号
FIL_PAGE_NEXT4字节下一个页的页号
FIL_PAGE_LSN8字节页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number)
FIL_PAGE_TYPE2字节该页的类型
FIL_PAGE_FILE_FLUSH_LSN8字节仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4字节页属于哪个表空间

对照着这个表格,我们看几个目前比较重要的部分:

  • FIL_PAGE_SPACE_OR_CHKSUM

    这个代表当前页面的校验和(checksum)。啥是个校验和?就是对于一个很长很长的字节串来说,我们会通过某种算法来计算一个比较短的值来代表这个很长的字节串,这个比较短的值就称为校验和。这样在比较两个很长的字节串之前先比较这两个长字节串的校验和,如果校验和都不一样两个长字节串肯定是不同的,所以省去了直接比较两个比较长的字节串的时间损耗。

  • FIL_PAGE_OFFSET

    每一个都有一个单独的页号,就跟你的身份证号码一样,InnoDB通过页号来可以唯一定位一个

  • FIL_PAGE_TYPE

    这个代表当前的类型,我们前边说过,InnoDB为了不同的目的而把页分为不同的类型,我们上边介绍的其实都是存储记录的数据页,其实还有很多别的类型的页,具体如下表:

    类型名称十六进制描述
    FIL_PAGE_TYPE_ALLOCATED0x0000最新分配,还没使用
    FIL_PAGE_UNDO_LOG0x0002Undo日志页
    FIL_PAGE_INODE0x0003段信息节点
    FIL_PAGE_IBUF_FREE_LIST0x0004Insert Buffer空闲列表
    FIL_PAGE_IBUF_BITMAP0x0005Insert Buffer位图
    FIL_PAGE_TYPE_SYS0x0006系统页
    FIL_PAGE_TYPE_TRX_SYS0x0007事务系统数据
    FIL_PAGE_TYPE_FSP_HDR0x0008表空间头部信息
    FIL_PAGE_TYPE_XDES0x0009扩展描述页
    FIL_PAGE_TYPE_BLOB0x000ABLOB页
    FIL_PAGE_INDEX0x45BF索引页,也就是我们所说的数据页

    我们存放记录的数据页的类型其实是FIL_PAGE_INDEX,也就是所谓的索引页

  • FIL_PAGE_PREVFIL_PAGE_NEXT

    我们前边强调过,InnoDB都是以页为单位存放数据的,有时候我们存放某种类型的数据占用的空间非常大(比方说一张表中可以有成千上万条记录),InnoDB可能不可以一次性为这么多数据分配一个非常大的存储空间,如果分散到多个不连续的页中存储的话需要把这些页关联起来,FIL_PAGE_PREVFIL_PAGE_NEXT就分别代表本页的上一个和下一个页的页号。这样通过建立一个双向链表把许许多多的页就都串联起来了,而无需这些页在物理上真正连着。需要注意的是,并不是所有类型的页都有上一个和下一个页的属性,不过我们本集中唠叨的数据页(也就是类型为FIL_PAGE_INDEX的页)是有这两个属性的,所以所有的数据页其实是一个双链表,就像这样:

    image-20230114110022062


七、File Trailer(文件尾部)

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

  • 前4个字节代表页的校验和

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

  • 后4个字节代表页面被最后修改时对应的日志序列位置(LSN)

    这个部分也是为了校验页的完整性的,只不过我们目前还没说LSN是个什么意思,所以大家可以先不用管这个属性。

这个File TrailerFile Header类似,都是所有类型的页通用的。


八、总结

  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值校验不成功的话,就说明同步过程出现了问题。

参考

mysql是怎样运行的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_72164.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在ONLYOFFICE中借助ChatGPT一键创建招聘启事的内容

大家好,相信和多人都在生活中或工作中看到过招聘启示,或多或少都会有些了解。今天教大家在ONLYOFFICE中怎样通过chetGPT创建一份满意的招聘启示,下面是我用chatgpt制作的一份招聘信息,请大家看一下。 ONLYOFFICE ONLYOFFICE文档是…

(HP)新手引导使用react-shepherd

1,官方参数文档:https://shepherdjs.dev/docs/tutorial-02-usage.html 2,基本代码 import { ShepherdTour } from react-shepherd; import ./index.less; // 自己的样式文件,用来修改样式 import ./shepherd.less; // 将shephe…

C++性能白皮书

最近看完了《C性能白皮书》,这本书列出了一些性能优化的思路,不过只是一些指引,没有讲具体细节,我整理出了其中的关键点分享给大家: 硬件篇 作为一个程序员,想要性能优化,最好要了解些硬件&…

为什么redis的zset用跳跃表而不用b+ tree?

这两天有小伙伴问我一个问题,为什么redis的zset用跳跃表,不用b tree? 我先不说结论,我先说下 跳跃表 和Btree 。 跳跃表 在之前的 《redis源码阅读-zset》 中,已经详解了zset的使用跳跃表的源码,今天借用…

hadoop3.*集群搭建,小白必看

hadoop广义上讲是一个大数据生态圈,接受大量处理、处理大量数据的一个全套的框架!hadoop3.x版本以后,主要有三大模块,HDFS、YARN、mapReduce这三大核心组成!什么是HDFS?分布式文件系统,hadoop集群的功能类…

数值方法笔记4:插值、近似和拟合

1. 插值1.1 插值的一些概念1.1.1 插值的定义1.1.2 插值的存在性1.1.3 插值的误差分析1.2 拉格朗日插值(Lagrange Interpolation)1.2.1 拉格朗日插值误差分析1.3 Newton多项式插值1.3.1 Newton多项式插值误差分析1.4 Chebyshev多项式确定插值点1.4.1 Chebyshev多项式性质1.5 有理…

内存映射(1)

内存映射 将磁盘文件中的数据映射到内存,用户通过修改内存就能修改磁盘文件 相关的系统调用: void *mmap() 功能:将一个文件或设备的数据映射到内存中 参数: void *addr : NULL 由内核指定length : 要映射的数据长度,…

JUC并发编程——进程与线程

目录一、进程和线程的概念1.1 进程1.2 线程1.3 进程与线程对比二、并行和并发的概念三、线程基本应用3.1 多线程应用——异步调用一、进程和线程的概念 1.1 进程 ● 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 …

【Mysql系列】Mysql之ACID实现原理

ACID 原子性 事务不可分割,要么全部执行,要么都不执行。原理是使用undo log。undo log,当事务对数据库进行修改的时候,会生成对应的undo log。 持久性 事务提交后,对于数据库的改变是永久性的。实现原理通过redo l…

超详细解读!数据库表分区技术全攻略

更多内容可以关注微信公众号:老程序员刘飞 分区的定义 分区是一种数据库优化技术,它可以将大表按照一定的规则分成多个小表,从而提高查询和维护的效率。在分区的过程中,数据库会将数据按照分区规则分配到不同的分区中&#xff0…

排序算法-java实现

文章目录冒泡排序选择排序插入排序快速排序希尔排序冒泡排序 原理: 依次比较两个相邻的元素,如果它们顺序错误就把它们交换过来。 时间复杂度: 若文件的初始状态是正序的,一趟扫描即可完成排序。所需的关键字比较次数C和记录移…

graphviz:实现图文件的可视化

1. graphviz下载安装 参考的是这篇文章:https://blog.csdn.net/qq_37085158/article/details/126421102 graphviz的下载地址为:https://graphviz.org/download/ 2. graphviz的使用步骤 将edge文件转化成dot文件WinR,输入cmd,在…

linux rsync服务端安装和windows客户端备份

安装:yum install -y rsync 密码内容:zhangsan:123456 配置文件:/etc/rsyncd.conf内容 # /etc/rsyncd: configuration file for rsync daemon mode # See rsyncd.conf man page for more options. # configuration example: uid root gi…

LVGL Styles

LVGL StylesGet started按钮添加标签按钮添加风格滑动条值显示StylesSize stylesBackground stylesBorder stylesOutline stylesShadow stylesImage stylesArc stylesText stylesLine stylesGet started 按钮添加标签 /*** brief 按钮事件回调函数* param e */ void btn_eve…

网络有线无线配置

一、需求 在无线接入区内,当Lsw1的上联口出现故障时,需要通过AP1-LSw1-LSw2-LSw3的路径访问公网server3。这是因为AP1通过无线网连接到LSw1,而LSw1与LSw3之间的链路出现故障,无法直接访问公网server3。因此,流量需要通…

一文说清WMS系统与MES系统,SRM系统,ERP系统集成的好处

由于制造过程的多样性、复杂性、业务流程的多样性和复杂性,因此,制造企业的信息化系统包括WMS、SRM、MES等管理系统,但它们的管理方向却各不相同,例如WMS这个是管理仓库、 SRM是管理公司的供应商、 MES是管理车间的生产制造的等等…

决策树、随机森林、GBDT、XGBoost

文章目录 1. 引入 1.1 决策树1.2 随机森林1.3 GBDT(Gradient Boosting Decision Tree)梯度提升决策树1.4 XGBoost(eXtreme Gradient Boosting)极端梯度提升2. 代码实现 2.1 决策树&随机森林&GBDT&XGBoost 2.1.1 分类2.1.2 回归2.1.3 显示模…

SpringCloud(二)配置中心

配置中心Nacos配置中心多环境共享Nacos集群搭建Nacos配置中心 作用: 统一配置管理配置自动刷新,热更新 实现: 统一配置管理 在nacos服务端,配置管理配置列表中新建配置了解配置获取的步骤: 项目启动->读取nacos中…

全开源无加密的RuleApp文章社区APP客户端源码

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 开源无加密的文章社区客户端源码分享 RuleApp文章社区,VIP会员,写作投稿积分商城,付费模块集成,多平台兼容这是一款开源免费,界…

最全es6数组方法

1.arr.push()从后面添加元素,返回值为添加完后的数组的长度 let arr [1,2,3,4,5] console.log(arr.push(5)) // 6 console.log(arr) // [1,2,3,4,5,5]2.arr.pop()从后面删除元素,只能是一个,返回值是删除的元素 let arr [1,2,3,4,5] console.log(arr.pop())//5 …