Mysql从入门到入神之(三)InnoDB的存储结构

前言

文本已收录至个人GitHub仓库,欢迎Star:github.com/bin39232820…
种一棵树最好的时间是十年前,其次是如今
我知道不少人不玩qq了,可是怀旧一下,欢迎加入六脉神剑Java菜鸟学习群,群聊号码:549684836 鼓励你们在技术的路上写博客mysql

絮叨

咱们继续来探索mysql。前面咱们了解了mysql的索引的一些基础知识,今天咱们来康康具体的InnoDB存储引擎git

InnoDB页的简介

InnoDB是一个将表中的数据存储到磁盘上的存储引擎,因此即便关机后重启咱们的数据仍是存在的。而真正处理数据的过程是发生在内存中的,因此须要把磁盘中的数据加载到内存中,若是是处理写入或修改请求的话,还须要把内存中的内容刷新到磁盘上。而咱们知道读写磁盘的速度很是慢,和内存读写差了几个数量级,因此当咱们想从表中获取某些记录时,InnoDB存储引擎须要一条一条的把记录从磁盘上读出来么?不,那样会慢死,InnoDB采起的方式是:将数据划分为若干个页,以页做为磁盘和内存之间交互的基本单位,InnoDB中页的大小通常为 16 KB。也就是在通常状况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。github

InnoDB行格式

这个意思就是咱们表中一行一行的数据格式,那么这些一行一行的数据格式是怎么样的呢?sql

设计InnoDB存储引擎的大佬们到如今为止设计了4种不一样类型的行格式,分别是Compact、Redundant、Dynamic和Compressed行格式,随着时间的推移,他们可能会设计出更多的行格式,可是无论怎么变,在原理上大致都是相同的。服务器

这边就随便了解一个就行了数据结构

COMPACT行格式

从图中能够看出来 这种行格式分为2个部分,第一个部分是记录这一行的额外信息,第二个部分就是记录的真实数据post

  • 变长字段长度列表
    • 咱们知道MySQL支持一些变长的数据类型,好比VARCHAR(M)、VARBINARY(M)、各类TEXT类型,各类BLOB类型,咱们也能够把拥有这些数据类型的列称为变长字段,变长字段中存储多少字节的数据是不固定的,因此咱们在存储真实数据的时候须要顺便把这些数据占用的字节数也存起来,这样才不至于把MySQL服务器搞懵,因此这些变长字段占用的存储空间分为两部分:
      • 真正的数据内容
      • 占用的字节数
    • 因此就是像前面说的,若是能肯定字符串的长度,尽可能使用定长的字段
  • NULL值列表
    • 咱们知道表中的某些列可能存储NULL值,若是把这些NULL值都放到记录的真实数据中存储会很占地方,因此Compact行格式把这些值为NULL的列统一管理起来,存储到NULL值列表中。
  • 记录头信息
    • 里面就是用来记录当前的行是否被删除,当前行的下一行的记录等等这写信息
  • 记录的真实数据
    • 除了当前表中字段的真实数据以外,还有不少其余的隐藏数据,咱们称之为隐藏列
      • row_id 行ID,惟一标识一条记录
      • transaction_id 事务ID
      • roll_pointer 回滚指针

上面就是咱们一行数据里面大概真实存放的东西了。接下来咱们来聊聊页,多个行组成的页。学习

索引(INDEX)页预览

索引页表明的这块16KB大小的存储空间能够被划分为多个部分,不一样部分有不一样的功能,各个部分如图所示:优化

User Records

在页的7个组成部分中,咱们本身存储的记录会按照咱们指定的行格式存储到User Records部分。可是在一开始生成页的时候,其实并无User Records这个部分,每当咱们插入一条记录,都会从Free Space部分,也就是还没有使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间所有被User Records部分替代掉以后,也就意味着这个页使用完了,若是还有新的记录插入的话,就须要去申请新的页了,这个过程的图示以下设计

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

User Records 里面存储的就是咱们前面说的行格式的数据 下面咱们来讲说它具体的东西吧

  • delete_mask 这个属性标记着当前记录是否被删除,占用1个二进制位,值为0的时候表明记录并无被删除,为1的时候表明记录被删除掉了。 当咱们下次再插入相同的数据的时候,会复用这个空间

  • min_rec_mask B+树的每层非叶子节点中的最小记录都会添加该标记

  • heap_no 这个属性表示当前记录在本页中的位置

  • record_type 这个属性表示当前记录的类型,一共有4种类型的记录,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。

  • next_record 这玩意儿很是重要,它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节即是下一条记录的真实数据。若是你熟悉数据结构的话,就当即明白了,这实际上是个链表,能够经过一条记录找到它的下一条记录。可是须要注意注意再注意的一点是,下一条记录指得并非按照咱们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。并且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录) ,为了更形象的表示一下这个next_record起到的做用,咱们用箭头来替代一下next_record中的地址偏移量:

从上面咱们能够看出 再页里面的行 其实就是一个链表,方便咱们去查询遍历。

Page Directory

这个干吗的呢,上面咱们说了 页里面的行能够作成一个链表去查询,可是要知道一个页16k 能够存储的数据会特别多,因此呢若是只是一个链表的话,那么查询的效率确定不高,因此mysql 把几个行记录分组一个组,一个组也叫一个槽,而后把这些槽用一个链表连起来,那么下次去查询的时候先去查槽,而后再去查组的这种形式。思想和跳表的思想很像哈哈。

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

  • 经过二分法肯定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。

  • 经过记录的next_record属性遍历该槽所在的组中的各个记录。

Page Header

为了能获得一个数据页中存储的记录的状态信息,好比本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特地在页中定义了一个叫Page Header的部分,它是页结构的第二部分,这个部分占用固定的56个字节,专门存储各类状态信息。

File Header

上边唠叨的Page Header是专门针对数据页记录的各类状态信息,比方说页里头有多少个记录了呀,有多少个槽了呀。咱们如今描述的File Header针对各类类型的页都通用,也就是说不一样类型的页都会以File Header做为第一个组成部分,它描述了一些针对各类页都通用的一些信息,比方说这个页的编号是多少,它的上一个页、下一个页是谁啦吧啦吧啦

这个我也稍微提一点 它里面有存 FIL_PAGE_PREV和FIL_PAGE_NEXT,这个是什么意思呢,

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

File Trailer

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

总结

文章内容出自 MySQL 是怎样运行的:从根儿上理解 MySQL,

结尾

咱们下章继续再战。

平常求赞

好了各位,以上就是这篇文章的所有内容了,能看到这里的人呀,都是真粉

创做不易,各位的支持和承认,就是我创做的最大动力,咱们下篇文章见

六脉神剑 | 文 【原创】若是本篇博客有任何错误,请批评指教,不胜感激 !

相关文章
相关标签/搜索