暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

MySQL之InnoDB存储结构总结

GrowthDBA 2021-11-16
948

它来啦,它来啦。。。InnoDB存储结构总结它来啦!前段时间,我们一起学习了InnoDB存储引擎记录结构、数据页结构、B+树索引、表空间结构的相关知识,大家肯定被里面N多个属性绕晕了。其实俺也一样,在此,为了方便理解和记忆,决定写一个总结性的文章来概述一下整个结构。

其次,浅谈一下数据结构相关知识,我不是开发人员,所以这方面的知识深度不够,大佬请轻喷。数据结构其实就是存储数据的格式(数据存储抽象出来的逻辑结构),每种数据结构有自己的特点,使用哪种数据格式需要根据具体的需求来选,比如MySQL的数据结构就是树(Tree)结构中的B+树(B+Tree)。

浅谈数据结构



程序 = 算法 + 数据结构。数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。可分为八大数据结构:数组(Array)栈(Stack)链表(Linked List)图(Graph)散列表(Hash)队列(Queue)树(Tree)堆(Heap)

小提示
还是有必要提一下时间复杂度的概念。这是百度百科给出的定义:在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。
看上面很官方的描述,有点不太好理解,大白话的理解说一下,仅供大家参考,时间复杂度简言之就是你写一个代码,它解决一个问题上需要走多少步骤,需要花费多长时间,也可以说成算法中基本操作的执行次数,即评估算法时间效率的一种形式。当然值越小表示时间效率越高(即得到结果的步骤和时间越少性能越好)。

数组(Array)

数组是最基本的数据结构,很多语言都内置支持数组。数组是使用一块连续的内存空间保存数据,存储一组具有相同类型的数据,保存的数据个数在分配内存的时候就是确定的。

  • 访问数组中第n个数据(即根据数据的下标随机访问)的时间复杂度是O(1);
  • 但是要在数组中查找一个指定的数据则是O(N);
当向数组中插入或者删除数据的时候:
  • 最好的情况是在数组的末尾进行操作,时间复杂度是O(1) ;
  • 最坏情况是插入或者删除第一个数据,时间复杂度是 O(N) 。在数组的任意位置插入或者删除数据的时候,后面的数据全部需要移动,移动的数据还是和数据个数有关所以总体的时间复杂度仍然是 O(N) 。
  • 平均情况时间复杂度也为O(n)。

特点:按照下标(索引)查询速度快、遍历数组方便。

限制

  • 数组大小固定后无法扩容;

  • 数组只能存储一种类型的数据;

  • 添加删除慢(需要移动其它元素);

使用场景:频繁查询,对存储空间要求不大、增加删除少的场景。

栈(Stack)

栈或者称为堆栈,栈是一种特殊的线性表(桶状线性数据结构)。仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作 。

特点:先进后出(FILO);

限制:只允许操作栈顶,不允许操作栈底;

使用场景:栈常应用于实现递归功能方面的场景,例如斐波那契数列。

队列(Queue)

队列实现了先入先出的语义,队列也可以使用数组和链表来实现。队列也是一种线性表 。不同的是,队列可以在一端添加元素,在另一端取出元素 。

队列只允许在队尾添加数据,在队头删除数据。但是可以查看队头和队尾的数据。还有一种是双端队列,在两端都可以插入和删除:

特点:先进先出(FIFO);
使用场景:因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。

链表(Linked List)

链表是在非连续的内存单元中保存数据,并且通过指针将各个内存单元链接在一起,最右一个节点的指针指向NULL。链表不需要提前分配固定大小存储空间,当需要存储数据的时候分配一块内存并将这块内存插入链表中。

链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个节点,一个是存储元素的数据域(内存空间),另一个是指向下一个节点的指针域。根据指针的指向,链表能形成不同的结构

在链表中查找第n个数据以及查找指定的数据的时间复杂度是O(N),但是插入和删除数据的时间复杂度是O(1),因为只需要调整指针就可以:

像上面这样的链表结构在插入和删除的时候编程会比较困难,因为需要记住当前节点的前一个节点,这样才能完成插入和删除。为了简便通常使用带有头节点的链表:

上面的链表是单链表,此外还有双链表,就是节点中包含指向下一个节点的指针和指向上一个节点的指针:

不带有头节点的双向链表在插入和删除数据的时候也不会出现单链表那样的问题。此外还有一种链表是循环链表,它是将双向链表的头尾相接:

向循环双向链表和循环链表中插入或者从中删除数据只是多移动几个指针。
特点
  • 很常用的一种数据结构,不需要初始化容量,可以任意加减元素;
  • 添加或删除元素时只需要改变前后两个元素节点的指针域指向地址即可,所以添加删除速度很快。
限制
  • 因含有大量的指针域,占用空间较大;
  • 查找元素需要遍历链表来查找,非常耗时。

使用场景:数据量较小,需要频繁添加删除操作的场景。

散列表(Hash)

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。针对某个数据的等值查询时间复杂度为O(1)。

图(Graph)

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。在图中的数据元素,我们称之为顶点(Vertex),顶点集合有穷非空。在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

因为我们的MySQL没有涉及到图这种数据结构,所以我对图这种数据结构也不是很了解,想要了解的话,请移步:https://www.jianshu.com/p/bce71b2bdbc8

堆(Heap)

堆(Heap)是计算机科学中一类特殊的数据结构的统称,堆通常是一个可以被看做一棵完全二叉树的数组对象,根节点可以为大于等于任何子节点(也可以小于等于任意子节点,看具体的排序方法),在存取时没有任何限制,可以随意的访问某一个子节点。最大堆:每个节点的值都大于等于它的孩子节点。最小堆:每个节点的值都小于等于它的孩子节点。

特点

  • 堆中某个节点的值总是不大于或不小于其父节点的值;

  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

树(Tree)

树是由节点或顶点和边组成的(可能是非线性的)且不存在着任何环的一种数据结构。MySQL、Oracle等关系型数据库的数据一般都是用树形结构存储的。
特点
  • 每个节点有零个或多个子节点;
  • 没有父节点的节点为根节点;
  • 每个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树。
树又分为很多种类,因使用场景的不同被定义出很多类型
  • 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;
  • 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;
  • 二叉树:每个节点最多含有两个子树的树称为二叉树;
  • 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个节点的二叉树,当且仅当其每一个节点都与深度为K的满二叉树中编号从1至n的节点一一对应时称之为完全二叉树。

数据结构小结

其实数据结构是为了方便大家理解数据存储方式的一种逻辑图示的表示方式,真实物理存储就像我们之前学习的MySQL之B+树索引,InnoDB存储引擎的B+树也是一种树形结构,组织的方式是通过目录项,目录项中又包含主键和页号。再来回忆一下这张图。




InnoDB存储结构总结



前段时间我们学习了MySQL之InnoDB记录结构MySQL之数据页结构MySQL之B+树索引MySQL之InnoDB表空间这些知识,从微观角度的一行记录格式一直到宏观的表空间结构都有了较全面的了解,但是里面各种属性、包括属性所占用的字节数等等知识点太多,记不住。其实,工作中我们也很少用到那些个属性,所以,暂时先把那些记不住的属性忘掉,我们把重要的知识点总结整理一下。

区(Extent)

InnoDB中有很多不同种类的页(Page),Page的大小为16KB,为了方便管理这些页,InnoDB提出了区(Extent)的概念,连续的64个页就是一个区。每256个区被划分为1个组。

  • 1个区 = 64个页 = 1MB

  • 1个组 = 256个区 = 256MB

extent 0 ~ extent 255这256个区是第1组,extent 256 ~ extent 511这256个区是第2组,extent 512 ~ extent 767这256个区是第3组,以此类推。

第1组最开始3个Page(即extent 0这个区最开始的3个Page)类型是固定的,分别是:
  • FSP_HDR类型:用来登记整个表空间的一些整体属性以及本组所有的区(就是extent 0 ~ extent 255第1组这256个区)的属性。整个表空间只有一个FSP_HDR类型的页面。
  • IBUF_BITMAP类型:用来存储本组所有区的所有页面关于INSERT BUFFER的信息。
  • INODE类型:用来存储许多称为INODE的数据结构。
其余各组最开始的2个Page(即剩余各组的第1个区的前2个Page)的类型是固定的:
  • XDES类型:全称是extent descriptor,用来登记本组256个区的属性。
  • IBUF_BITMAP类型:同上,不再赘述。
表空间被划分为许多连续的区,每个区默认由64个页组成,每256个区划分为一组,每个组的最开始的几个页面类型是固定的

段(Segment)

InnoDB存储引擎的表只有一个聚簇索引,一个索引会生成2个段(存放叶子节点的区的集合、存放非叶子节点的区的集合),段是以区为单位申请存储空间的,对于存储记录比较少的表这样的申请方式很浪费。所以,段对于数据量较小的表太浪费存储空间的这种情况,提出了碎片(fragment)区的概念:在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在,在碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,不属于任何一个段。

为某个段分配存储空间的策略:

  • 在刚开始向表中插入数据的时候,段是先从某个碎片区以单个页面为单位来分配存储空间

  • 当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间

段是一些零散的页面以及一些完整的区的集合

区的分类

表空间的是由若干个区组成,这些大体上可以分为4种类型

  • 空闲的区(FREE):现在还没有用到这个区中的任何页面。

  • 有剩余空间的碎片区(FREE_FRAG):碎片区中还有可用的页面。

  • 没有剩余空间的碎片区(FULL_FRAG):碎片区中的所有页面都被使用,没有空闲页面。

  • 附属于某个段的区(FSEG):每一个索引都可以分为叶子节点段和非叶子节点段,除此之外还会定义一些特殊作用的段,在这些段中的数据量很大时将使用区来作为基本的分配单位。

处于FREE、FREE_FRAG以及FULL_FRAG这三种状态的区是独立的,直属于表空间;而处于FSEG状态的区是附属于某个段

XDES Entry链表

向表中插入数据本质上就是向表中各个索引的叶子节点段、非叶子节点段插入数据,不同的区有不同的状态,看一下向某个段中插入数据的过程:段中数据较少 → 查看表空间中是否有FULL_FRAG的区 → 如有,取一些零散的页把数据插进去。否则,到表空间申请一个状态为FREE的区,状态变为FREE_FRAG,然后从该新申请的区中取一些零散的页把数据插进去 → 之后不同的段使用零散页的时候都会从该区中取,直到该区中没有空闲空间,然后该区的状态就变成了FULL_FRAG。

为区分直属表空间中区的状态,通过XDES Entry结构中List Node的指针将不同状态的区分为3个链表

  • 把状态为FREE的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表称之为FREE链表
  • 把状态为FREE_FRAG的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表称之为FREE_FRAG链表
  • 把状态为FULL_FRAG的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FULL_FRAG链表

除了直属表空间的区外,我们还再考虑一下属于某个段的区状态,InnoDB为每个段中的区对应的XDES Entry结构也建立了3个链表

  • FREE链表:同一个段中,所有页面都是空闲的区对应的XDES Entry结构会被加入到这个链表。注意和直属于表空间的FREE链表区别开了,此处的FREE链表是附属于某个段的。
  • NOT_FULL链表:同一个段中,仍有空闲空间的区对应的XDES Entry结构会被加入到这个链表。
  • FULL链表:同一个段中,已经没有空闲空间的区对应的XDES Entry结构会被加入到这个链表。

综上所述,1张含有1个聚簇索引、1个二级索引的表,2个索引共4个段,每个段有3个链表,共12个链表,加上直属表空的3个链表,整个独立表空间共需维护15个链表

InnoDB数据页结构


上面5张图,可以简单回忆起大概的数据页结构。
Infimum+Supremum & User Records:Infimum和Supremum是InnoDB定义的两条伪记录,分别为最小记录与最大记录,都是由5字节大小的记录头信息和8字节大小的一个固定的部分组成,共计26字节。User Records为用户真实记录。
Page Directory:将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组,每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这就是所谓的Page Directory,也就是页目录。页面目录中的这些地址偏移量被称为槽(英文名:Slot)。在一个页中根据主键查找记录是非常快的,分为两步:①通过二分法确定该记录所在的槽。②通过记录的next_record属性遍历该槽所在的组中的各个记录
Page Header:占用固定的56个字节,专门存储数据页的各种状态信息(如:页目录的插槽数、本页中的记录的数量、已删除记录占用的字节数、最后插入记录的位置、当前页在B+树中所处的层级等信息)。
File Header:占用固定的38个字节,针对各种类型的页都通用,记录这个页的信息(如:页的编号是多少、它的上一个页、下一个页是谁、页的类型、这个页属于哪个表空间等信息)。每个数据页的File Header部分都有上一个和下一个页的编号,所以所有的数据页会组成一个双链表。
File Trailer:由8个字节组成,前4个字节代表页的校验和,和File Header中的校验和相对应,当一个页面在内存中修改了,在同步之前就要把它的校验和算出来,因为File Header在页面的前边,所以校验和会被首先同步到磁盘,当完全写完时,校验和也会被写到页的尾部,如果完全同步成功,则页的首部和尾部的校验和应该是一致的。后4个字节代表页面被最后修改时对应的日志序列位置(LSN),这个部分也是为了校验页的完整性(为保证从内存中同步到磁盘的页的完整性,在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的LSN值,如果首部和尾部的校验和和LSN值校验不成功的话,就说明同步过程出现了问题)。File Trailer与File Header类似,都是所有类型的页通用的。

InnoDB记录结构

记录的额外信息由变长字段长度列表、NULL值列表、记录头信息组成。

  • 变长字段长度列表:在Compact行格式中,所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放

  • NULL值列表:Compact行格式把值允许为NULL的列统一管理起来,将每个允许存储NULL的列对应一个二进制位,二进制位按照列的顺序逆序排列。二进制位的值为1时,代表该列的值为NULL,反之亦然。NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0

  • 记录头信息:用于描述记录的记录头信息,固定大小5个字节(40二进制位)。record_type:当前记录的类型,一共有4种类型的记录,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。next_record:从当前记录的真实数据到下一条记录的真实数据的地址偏移量。

记录的真实数据:除了记录的真实数据外,还有固定6字节大小的DB_ROW_ID(行ID)、6字节大小的DB_TRX_ID(事务ID)、7字节大小的DB_ROLL_PTR(回滚指针)。

  • 当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出。Compact和Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址。

  • 行溢出的临界点:如果一个列中存储的数据小于8099个字节,那么该列就不会成为溢出列,否则该列就需要成为溢出列。不过这个8099个字节的结论只是针对只有一个列的表来说的,如果表中有多个列,那结论就不一致了,所以重点就是:你不用关注这个临界点是什么,只要知道如果我们一条记录的某个列中存储的数据占用的字节数非常多时,该列就可能成为溢出列。

  • Dynamic和Compressed行格式类似于COMPACT行格式,在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字段真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。

  • 在列的值允许为NULL的情况下,gbk字符集下M的最大取值就是32766,utf8字符集下M的最大取值就是21844,这都是在表中只有一个字段的情况下说的,一定要记住一个行中的所有列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节。

  • 变长字符集的CHAR(M)类型的列要求至少占用M个字节,而VARCHAR(M)却没有这个要求。比方说对于使用utf8字符集的CHAR(10)的列来说,该列存储的数据字节长度的范围是10~30个字节。即使我们向该列中存储一个空字符串也会占用10个字节,这是怕将来更新该列的值的字节长度大于原有值的字节长度而小于10个字节时,可以在该记录处直接更新,而不是在存储空间中重新分配一个新的记录空间,导致原有的记录空间成为所谓的碎片。




InnoDB的B+树索引



B+树索引

  • 聚簇索引(Clustered Index)

目录项中的两个列是主键和页号。1、使用记录主键值的大小进行记录和页的排序:①页内的记录是按照主键的大小顺序排成一个单向链表;②各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表;③存放目录项记录的页分为不同的层次在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表。2、B+树的叶子节点存储的是完整的用户记录
  • 二级索引(Secondary Index)

这个B+树与上边的聚簇索引有几处不同:1、使用记录c2列的大小进行记录和页的排序,这包括三个方面的含义:①页内的记录是按照c2列的大小顺序排成一个单向链表;②各个存放用户记录的页也是根据页中记录的c2列大小顺序排成一个双向链表;③存放目录项记录的页分为不同的层次在同一层次中的页也是根据页中目录项记录的c2列大小顺序排成一个双向链表。2、B+树的叶子节点存储的并不是完整的用户记录,而只是c2列+主键这两个列的值。3、目录项记录中不再是主键+页号的搭配,而变成了c2列+页号的搭配

如果我们想根据c2列的值查找到完整的用户记录的话,仍然需要到聚簇索引中再查一遍这个过程被称为回表

  • 联合索引(复合索引)

可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照c2和c3列的大小进行排序:1、每条目录项记录都由c2、c3、页号这三个部分组成,各条记录先按照c2列的值进行排序,如果记录的c2列相同,则按照c3列的值进行排序;2、B+树叶子节点处的用户记录由c2、c3和主键c1列组成联合索引,本质上也是一个二级索引

InnoDB的B+树索引注意事项

  • 根页面永不动窝:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建立一个索引,那么它的根节点的页号便会被记录到某个地方,然后凡是InnoDB存储引擎需要用到这个索引的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引。
  • 内节点中目录项记录的唯一性:B+树索引的内节点中目录项记录的内容是索引列 + 页号的搭配,但是对于二级索引来说,会出现重复值的情况。为了让新插入记录能找到自己在那个页里,我们需要保证在B+树的同一层内节点的目录项记录除页号这个字段以外是唯一的。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的:索引列的值、主键值、页号。也就是我们把主键值也添加到二级索引内节点中的目录项记录了,这样就能保证B+树每一层节点中各条目录项记录除页号这个字段外是唯一的。
  • 一个页面最少存储2条记录:B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录。如果一个大的目录中只存放一个子目录,目录层级非常非常多,而且最后的那个存放真实数据的目录中只能存放一条记录,这样就会造成极大的性能损耗,所以,InnoDB的一个数据页至少可以存放两条记录。

模拟面试问答加深对InnoDB的B+树索引的理解

简单总结一下上面的知识点

  • 每个索引都对应一棵B+树,B+树分为好多层,最下边一层是叶子节点,其余的是内节点。所有用户记录都存储在B+树的叶子节点,所有目录项记录都存储在内节点;

  • InnoDB存储引擎会自动为主键(如果没有它会自动帮我们添加)建立聚簇索引,聚簇索引的叶子节点包含完整的用户记录;

  • 我们可以为自己感兴趣的列建立二级索引,二级索引的叶子节点包含的用户记录由索引列 + 主键组成,所以如果想通过二级索引来查找完整的用户记录的话,需要通过回表操作,也就是在通过二级索引找到主键值之后再到聚簇索引中查找完整的用户记录;

  • B+树中每层节点都是按照索引列值从小到大的顺序排序而组成了双向链表,而且每个页内的记录(不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单链表。如果是联合索引的话,则页面和记录先按照联合索引前边的列排序,如果该列值相同,再按照联合索引后边的列排序;

  • 通过索引查找记录是从B+树的根节点开始,一层一层向下搜索。由于每个页面都按照索引列的值建立了Page Directory(页目录),所以在这些页面中的查找非常快。

因为B+树的知识点是重中之重,在有了上面数据结构和B+树索引相关的知识的前提下,我们再来进行模拟面试的一问一答,再次加深对B+树索引的理解和理解InnoDB为什么使用B+树来存储索引:

Q-1:MySQL的InnoDB存储索引用的什么数据结构?

A-1:B+树。

Q-2:B+树的查询时间大概多少?

A-2:和树的高度有关,大概是log(n)。

Q-3:Hash表存储索引,查询时间是多少?

A-3:Hash的话,平均时间O(1)。

Q-4:既然Hash比B+树更快,为什么MySQL数据库InnoDB存储引擎使用B+树来存索引呢?

再唠叨一些数据结构的相关知识:二叉排序树:左边比根节点小,右边比根节点大,并且左右子树都是二叉排序树。

在一些极端情况下,比如插入序列是有序的,就会出现退化情况,有序序列,二叉排序树退化成链表。

所以就需要平衡树,在插入的时候调整整棵树,让它的节点尽可能均匀分布。红黑树就是平衡树的一种,其复杂的定义和规则,都是为了保证树的平衡性。树的查找性能取决于树的高度,让树尽可能平衡,就是为了降低树的高度。

B树:一种多路搜索树,它的每个节点可以拥有多于两个孩子节点。M路的B树最多能拥有M个孩子节点。路数越多,树的高度越低,但是不可以设计成无限多路,如果不限制路数,B树就退化成一个有序数组了。B树多用于文件系统的索引,文件系统和数据库的索引都是存在硬盘上的,如果数据量大的话,不一定能一次性加载进内存,每次可以加载B树的一个节点,然后一步一步往下找。假设内存一次性只能加载2个数,这么长的有序数组是无法一次性加载进内存的。

可以把它组织为一颗三路的B树,这样每个节点最最多有2个数。

查找的时候,每次载入一个节点进内存就行。如:

如果在内存中,红黑树比B树效率更高,但是涉及到磁盘操作,B树就更优了。

B+树:B+树是在B树的基础上进行改造,它的数据都在叶子节点,同时叶子节点之间还加了指针形成链表。为什么会这样设计?是因为,MySQL数据库中SELECT数据,不一定只查询1条数据,很多时候会查询多条,如果是多条的话,B树需要做局部的中序遍历,可能要跨层访问。而B+树由于所有数据都在叶子节点,不用跨层,同时由于有链表结构,只需要找到首尾,通过链表就能把所有数据取出来了。

A-4:和业务场景有关。如果只查询一个数据,确实是Hash更快,但是数据库中经常会查询多条,这时候由于 B+ 树索引有序,并且又有链表相连,它的查询效率比 Hash 就快很多了。而且数据库中的索引一般是在磁盘上,数据量大的情况可能无法一次装入内存,B+ 树的设计可以允许数据分批加载,同时树的高度较低,提高查找效率。



小结



今天,我们抛开了前面4篇文章介绍的繁杂属性,对InnoDB存储引擎的存储结构做了一个总结,浅谈了一些数据结构的知识,总结了一下表空间、数据页结构、记录结构,同时,也总结了B+树索引。
数据结构其实就是存储数据的格式(数据存储抽象出来的逻辑结构)。比如二级索引,通过单个非叶子节点存储的多个(索引列的值+主键值+页号)组成的目录项,多个非叶子(叶子)节点间存储的偏移量指针,共同构成了逻辑上的B+树结构。通过模拟面试的问答环节,知道了MySQL这样设计数据结构的缘由,不得不佩服MySQL的创始人和开发人员。
表空间结构章节涉及的属性和概念比较多,《MySQL是怎样运行的:从根儿上理解MySQL》读者群中,有一个读者把整个表空间结构整合了一张大图。

为方便大家学习查阅,我把高清原图分享在百度网盘上,大家可以按需下载。(链接: https://pan.baidu.com/s/1qozLvB8H4MbGNzKSqgvfjg,提取码: rmlu。)
今天就到这里吧,我们下篇见!~



 参考资料 



  • 小孩子4919《MySQL是怎样运行的:从根儿上理解MySQL》

  • channingbreeze-'51CTO技术栈'公众号-《为什么MySQL数据库要用B+树存储索引?》

end


文章转载自 GrowthDBA,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论