西安 网站建设 费用,网站建设实现后台数据导出excel,谷歌seo优化中文章,长沙官网制作「mysql是怎样运行的」第五章 盛放记录的大盒子—InnoDB数据页结构 文章目录「mysql是怎样运行的」第五章 盛放记录的大盒子---InnoDB数据页结构[toc]一、不同类型的页介绍二、数据页结构的快速浏览三、记录在页中的存储记录头信息的秘密四、Page Directory(页目录)五、Page He…「mysql是怎样运行的」第五章 盛放记录的大盒子—InnoDB数据页结构
文章目录「mysql是怎样运行的」第五章 盛放记录的大盒子---InnoDB数据页结构[toc]一、不同类型的页介绍二、数据页结构的快速浏览三、记录在页中的存储记录头信息的秘密四、Page Directory(页目录)五、Page Header(页面头部)六、File Header(文件头部)七、File Trailer(文件尾部)八、总结
一、不同类型的页介绍
前边我们简单提了一下页的概念它是InnoDB管理存储空间的基本单位一个页的大小一般是16KB。InnoDB为了不同的目的而设计了许多种不同类型的页比如存放表空间头部信息的页存放Insert Buffer信息的页存放INODE信息的页存放undo日志信息的页等等等等。
我们聚焦的是那些存放我们表中记录的那种类型的页官方称这种存放记录的页为索引(INDEX)页鉴于我们还没有了解过索引是个什么东西而这些表中的记录就是我们日常口中所称的数 据所以目前还是叫这种存放记录的页为数据页吧。 二、数据页结构的快速浏览
数据页代表的这块16KB大小的存储空间可以被划分为多个部分不同部分有不同的功能各个部分如图所示: 从图中可以看出一个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部分替代掉之后也就意味着这个页使用完了如果还有新的 记录插入的话就需要去申请新的页了这个过程的图示如下: 为了更好的管理在User Records中的这些记录InnoDB可费了一番力气呢在哪费力气了呢?不就是把记录按照指定的行格式一条一条摆在User Records部分么?其实这话还得从记录行格式的记录头信 息中说起。 记录头信息的秘密 从图中可以看到我们特意把记录头信息的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的大叔们为我们的记录也制作了一个类似的目录他们的制作过程是这样的:
将所有正常的记录(包括最大和最小记录不包括标记为已删除的记录)划分为几个组。每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录也就是该组内共有几条记录。将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方这个地方就是所谓的Page Directory也就是页目录(此时应该返回头看看页面各个部分的图)。页面目录 中的这些地址偏移量被称为槽(英文名:Slot)所以这个页面目录就是由槽组成的。
比方说现在的page_demo表中正常的记录共有6条InnoDB会把它们分成两组第一组中只有一个最小记录第二组中是剩余的5条记录看下边的示意图: 现在页目录部分中有两个槽也就意味着我们的记录被分成了两个组槽1中的值是112代表最大记录的地址偏移量就是从页面的0字节开始数数112个字节槽0中的值是99代表最小记录的地址偏移量。注意最小和最大记录的头信息中的n_owned属性 最小记录的n_owned值为1这就代表着以最小记录结尾的这个分组中只有1条记录也就是最小记录本身。最大记录的n_owned值为5这就代表着以最大记录结尾的这个分组中只有5条记录包括最大记录本身还有我们自己插入的4条记录。
99和112这样的地址偏移量很不直观我们用箭头指向的方式替代数字这样更易于我们理解所以修改后的示意图就是这样 是的设计InnoDB的大叔们对每个分组中的记录条数是有规定的对于最小记录所在的分组只能有 *1* 条记录最大记录所在的分组拥有的记录条数只能在 *1~8* 条之间剩下的分组中记录的条数范围只能在是 *4~8* 条之间。所以分组是按照下边的步骤进行的
初始情况下一个数据页里只有最小记录和最大记录两条记录它们分属于两个分组。之后每插入一条记录都会从页目录中找到主键值比本记录的主键值大并且差值最小的槽然后把该槽对应的记录的n_owned值加1表示本组内又添加了一条记录直到该组中的记录数等于8个。在一个组中的记录数等于8个后再插入一条记录时会将组中的记录拆分成两个组一个组中4条记录另一个5条记录。这个过程会在页目录中新增一个槽来记录这个新增分组中最大的那条记录的偏移量。 数据页中查找指定主键值的记录的过程 一个数据页中查找指定主键值的记录的过程分为两步
通过二分法确定该记录所在的槽并找到该槽中主键值最小的那条记录。通过记录的next_record属性遍历该槽所在的组中的各个记录。 五、Page Header(页面头部)
设计InnoDB的大叔们为了能得到一个数据页中存储的记录的状态信息比如本页中已经存储了多少条记录第一条记录的地址是什么页目录中存储了多少个槽等等特意在页中定义了一个叫Page Header的部分它是页结构的第二部分这个部分占用固定的56个字节专门存储各种状态信息具体各个字节都是干嘛的看下表:
名称占用空间大小描述PAGE_N_DIR_SLOTS2字节在页目录中的槽数量PAGE_HEAP_TOP2字节还未使用的空间最小地址也就是说从该地址之后就是Free SpacePAGE_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_DIRECTION和PAGE_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 NumberFIL_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_PREV和FIL_PAGE_NEXT 我们前边强调过InnoDB都是以页为单位存放数据的有时候我们存放某种类型的数据占用的空间非常大比方说一张表中可以有成千上万条记录InnoDB可能不可以一次性为这么多数据分配一个非常大的存储空间如果分散到多个不连续的页中存储的话需要把这些页关联起来FIL_PAGE_PREV和FIL_PAGE_NEXT就分别代表本页的上一个和下一个页的页号。这样通过建立一个双向链表把许许多多的页就都串联起来了而无需这些页在物理上真正连着。需要注意的是并不是所有类型的页都有上一个和下一个页的属性不过我们本集中唠叨的数据页也就是类型为FIL_PAGE_INDEX的页是有这两个属性的所以所有的数据页其实是一个双链表就像这样 七、File Trailer(文件尾部)
我们知道InnoDB存储引擎会把数据存储到磁盘上但是磁盘速度太慢需要以页为单位把数据加载到内存中处理如果该页中的数据在内存中被修改了那么在修改后的某个时间需要把数据同步到磁盘中。但是在同步了一半的时候中断电了咋办这不是莫名尴尬么为了检测一个页是否完整也就是在同步的时候有没有发生只同步一半的尴尬情况设计InnoDB的大叔们在每个页的尾部都加了一个File Trailer部分这个部分由8个字节组成可以分成2个小部分 前4个字节代表页的校验和 这个部分是和File Header中的校验和相对应的。每当一个页面在内存中修改了在同步之前就要把它的校验和算出来因为File Header在页面的前边所以校验和会被首先同步到磁盘当完全写完时校验和也会被写到页的尾部如果完全同步成功则页的首部和尾部的校验和应该是一致的。如果写了一半儿断电了那么在File Header中的校验和就代表着已经修改过的页而在File Trialer中的校验和代表着原先的页二者不同则意味着同步中间出了错。 后4个字节代表页面被最后修改时对应的日志序列位置LSN 这个部分也是为了校验页的完整性的只不过我们目前还没说LSN是个什么意思所以大家可以先不用管这个属性。
这个File Trailer与File Header类似都是所有类型的页通用的。 八、总结
InnoDB为了不同的目的而设计了不同类型的页我们把用于存放记录的页叫做数据页。一个数据页可以被大致划分为7个部分分别是 File Header表示页的一些通用信息占固定的38字节。Page Header表示数据页专有的一些信息占固定的56个字节。Infimum Supremum两个虚拟的伪记录分别表示页中的最小和最大记录占固定的26个字节。User Records真实存储我们插入的记录的部分大小不固定。Free Space页中尚未使用的部分大小不确定。Page Directory页中的某些记录相对位置也就是各个槽在页面中的地址偏移量大小不固定插入的记录越多这个部分占用的空间越多。File Trailer用于检验页是否完整的部分占用固定的8个字节。 每个记录的头信息中都有一个next_record属性从而使页中的所有记录串联成一个单链表。InnoDB会为把页中的记录划分为若干个组每个组的最后一个记录的地址偏移量作为一个槽存放在Page Directory中所以在一个页中根据主键查找记录是非常快的分为两步 通过二分法确定该记录所在的槽。通过记录的next_record属性遍历该槽所在的组中的各个记录。 每个数据页的File Header部分都有上一个和下一个页的编号所以所有的数据页会组成一个双链表。为保证从内存中同步到磁盘的页的完整性在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的LSN值如果首部和尾部的校验和和LSN值校验不成功的话就说明同步过程出现了问题。 参考 mysql是怎样运行的