InnoDB的B+树索引(一)

文章目录

    • 概要
    • 一、InnoDB行格式
    • 二、InnoDB数据页结构
      • 2.1 User Records
      • 2.2 两个虚拟行记录
      • 2.3 PageDirectory(页目录)
      • 2.4 File Header(文件头部)
    • 三、B+树索引
      • 3.1 B+树索引结构
      • 3.2 先有根节点再有叶子节点
      • 3.3 一条记录在索引中的查找过程

概要

当我们从表中获取某些记录时,InnoDB采取的方式是:将数据划分为若干个,以作为磁盘和内存之间交互的基本单位的大小一般为 16 KB。一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
那么,一条数据在B+树的查找过程是怎么样的?
本文主要是围绕这个问题来总结。
说明,本文主要参考了:《MySQL是怎样运行的》

一、InnoDB行格式

向表中插入一条数据,这条记录在磁盘上的存放方式被称为行格式或者记录格式。InnoDB存储引擎设计了4种不同类型的行格式,分别是CompactRedundantDynamicCompressed行格式。

CREATE TABLE `index_demo` (
  `c1` int NOT NULL,
  `c2` int DEFAULT NULL,
  `c3` char(1) DEFAULT NULL,
  PRIMARY KEY (`c1`)
) ENGINE=InnoDB ROW_FORMAT=COMPACT;

上面创建了一张表,并且指定了行格式为CompactCompact格式用图表示如下:
在这里插入图片描述
上面可以看出,该格式主要由两部分组成:真实数据跟额外信息。本文主要是关于索引,所以这里不会展开详细介绍各个部分的具体信息,主要把跟索引相关部分进行重点说明。

上面的表数据中,在行格式上的表现如下:
在这里插入图片描述
这里需要重点关注头信息中的以下部分:

  • min_rec_mask: B+树的每层非叶子节点中的最小记录都会添加该标记
  • heap_no:表示当前记录在本页中的位置
  • record_type: 表示当前记录的类型,0:普通记录,1:B+树非叶子节点记录,2:最小记录,3:最大记录
  • next_record: 表示下一条记录的相对位置

mysql8.0默认采用的行格式是Dynamic,这个格式跟Compact格式很像,只是在在处理行溢出数据时有点不一样。不管是哪个格式,都不影响理解InnoDB索引。

二、InnoDB数据页结构

,是InnoDB管理存储空间的基本单位,一个的大小一般是16KBInnoDB为了不同的目的而设计了许多种不同类型的页,比如存放表空间头部信息的页,存放undo日志信息的页等。存放表中记录页,称为索引(INDEX)页。这里主要是介绍这种
以下就是的结构:
在这里插入图片描述
这里我们需要重点关注:User Records,虚拟行记录(Infimum+Supremum),File Header(页的通用信息),PageDirectory

2.1 User Records

用户存储的记录会按照用户指定的行格式存储到User Records部分。但是在一开始生成页的时候,其实并没有User Records这个部分,每当插入一条记录,就会从Free Space部分,申请一个记录大小的空间划分到User Records,当Free Space全部变成User Records部分时,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。
在这里插入图片描述
当我们向页中插入多条记录时,这些记录在User Record部分又是如何存储的呢?

为了方便理解,我们向上面的表中插入几条数据

INSERT INTO index_demo VALUES(1, 4,'u'), (3, 9, 'd'), (5, 3, 'y'),(8,7,'a');

User Record单独拎出来,那么这几条数据的存储大致如下:
在这里插入图片描述
上图可以看出,各记录通过主键值从小到大排序,并且通过链表链接在一起
这里需要注意:

  1. min_rec_mask都是0,B+树的每层非叶子节点中的最小记录都会添加该标记,插入的四条记录的min_rec_mask值都是0,意味着它们都不是B+树的非叶子节点中的最小记录。
  2. heap_no: 记录在本页中的位置,注意到这里是从2开始,因为还有2条虚拟记录:最小记录和最大记录
  3. next_record:表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节便是下一条记录的真实数据。下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录)

2.2 两个虚拟行记录

在一个INDEX页中,不管有没有用户数据,也不管存放了多少用户数据,INDEX页中一定存在两条伪记录,那就是最小记录与最大记录,这两条记录的构造十分简单,都是由5字节大小的记录头信息和X5字节大小的一个固定的部分组成的。
在这里插入图片描述
在这里插入图片描述
配合上最小记录跟最大记录,那么上边的数据存储如下:

在这里插入图片描述

2.3 PageDirectory(页目录)

通过上面已经了解到,记录在页中按照主键值由小到大顺序串联成一个单链表。假如现在有如下查询语句,那么InnoDB是如何在这个页中找到这条记录的呢?

select * from index_demo where c1 = 5;

一种做法是,从链表的最小记录开始遍历,根据next_record一直遍历找到的c1=5的记录。但是为了提高查询速度,InnoDB采用的是根据页码查找的方法,该方法的核心是制作出类似于目录的结构,以下是制作过程:

  1. 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。

  2. 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该组内共有几条记录。

  3. 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到Page Directory,也就是页目录。页面目录中的这些地址偏移量被称为槽(英文名:Slot),所以这个页面目录由槽组成。

比方说现在的index_demo表中正常的记录共有6条,InnoDB会把它们分成两组,第一组中只有一个最小记录,第二组中是剩余的5条记录,看下面的示意图:
在这里插入图片描述
为了展示上的美观,调整以下Page Directory的位置
在这里插入图片描述

InnoDB对每个分组中的记录条数是有规定的:最小记录所在的分组只能有 1 条记录,最大记录所在的分组在1~8条之间。其他的记录分组的记录条数在4到之间 。分组过程如下:

  • 初始情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组。

  • 之后每插入一条记录,都会从页目录中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应的记录的n_owned值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个。

  • 在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录中新增一个槽来记录这个新增分组中最大的那条记录的偏移量。

为了演示一条记录,通过槽在一个页内是如何查找的,下面增加多几条记录

在这里插入图片描述
这里行记录的头信息只保留了n_owned和next_record属性,也省略了记录之间的连线。现在一共有4各槽,他们的编号是01234。现在要从这些记录中找出主键值等于5的记录,过程如下:

  1. 二分法计算中间槽的位置:(0+4)/2=2,所以查看槽2对应记录的主键值为8,又因为8 > 5,所以设置high=2low保持不变。
  2. 重新计算中间槽的位置:(0+2)/2=1,所以查看槽1对应的主键值为4,又因为4 < 6,所以设置low=1high保持不变。
  3. 因为high - low的值为1,所以确定主键值为5的记录在槽2对应的组中。此刻需要找到槽2中主键值最小的那条记录,然后沿着单向链表遍历。但是,每个槽对应的记录都是该组中主键值最大的记录,槽2对应的是主键值为8的记录,如何找到一个组中最小的记录呢?因为槽是挨着的,索引可以很轻易的拿到槽1对应的记录(主键值为4),该条记录的下一条记录就是槽2中主键值最小的记录,该记录的主键值为5。也就是我们要找的记录,如果是其他记录,可以沿着链表再进行遍历,由于一个组中包含的记录条数只能是1~8条,所以遍历一个组中的记录是很快的

2.4 File Header(文件头部)

同类型的页都会以File Header作为第一个组成部分,它描述了一些针对各种页都通用的一些信息,比方说这个页的编号是多少,它的上一个页、下一个页指向等。该属性由很多内容组成,不过这里我们只要关注两个:FIL_PAGE_PREVFIL_PAGE_NEXT

InnoDB都是以为单位存放数据,如果数据占用的空间非常大,就会用到多个跟跟之间就通过这两个属性进行关联。FIL_PAGE_PREVFIL_PAGE_NEXT分别代表本页的上一个和下一个页的页号。
这样通过建立一个双向链表把许许多多的页就都串联起来了,而无需这些页在物理上真正连着。

在这里插入图片描述

三、B+树索引

上面已经介绍了,查找一条记录,在一个中是如何找到的,总结下来是如下两步:

  • 通过二分法确定该记录所在的槽。
  • 通过记录的next_record属性遍历该槽所在的组中的各个记录。

但实际的开发,一张表的数据一般都不止一个。那么,在多个页中,要找到一条数据,InnoDB又是如何查找的呢?

还是用上面提到的表index_demo作为演示,重新插入以下数据:

INSERT INTO index_demo VALUES(1, 4, 'u'), (3, 9, 'd'), (5, 3, 'y');
mysql> select * from index_demo;
+----+------+------+
| c1 | c2   | c3   |
+----+------+------+
|  1 |    4 | u    |
|  3 |    9 | d    |
|  5 |    3 | y    |
+----+------+------+

行格式只展示以下信息,并且为了展示的美观,将格式竖了起来:
在这里插入图片描述
那么上面这些记录在中的存储大致如下:
在这里插入图片描述

3.1 B+树索引结构

现在假设,每个数据页最多能存放3条记录(实际上一个数据页非常大,可以存放下好多记录),现在再向index_demo表中插入一条记录。

INSERT INTO index_demo VALUES(4, 4, 'a');

因为页10最多只能放3条记录,这时候就是需要再分配一个新页,同时,InnoDB中规定:下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值,基于这两点,重新调整数据在页中的位置只要经过以下3个步骤:

  1. 分配一个新页,页号为28
  2. 将主键值为5的记录(因为插入的主键值比5小)移动到到页28
  3. 将主键值为4的记录插入到页10中

经过以上步骤后,数据结构如下:
在这里插入图片描述
这里需要注意的是:数据页的编号可能并不是连续的

当我们向index_demo中插入多条数据后,重复以上步骤,其结构如下:

在这里插入图片描述
此时,我们想要查找一条数据,还要解决一个问题:怎么知道某一条记录在哪一个页中?

为了达到快速查找的目的,InnoDB使用了目录项的方式来管理跟主键的关系。同时这种目录项也是存储在中。其表示结构如下:

在这里插入图片描述
这里我们再回顾以下record_type属性的含义:

  • 0:普通的用户记录
  • 1:目录项记录
  • 2:最小记录
  • 3:最大记录

需要注意的是:

  1. 目录项记录的record_type值是1,普通用户记录的record_type值是0。
  2. 目录项只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,可能包含很多列,另外还有InnoDB自己添加的隐藏列。
  3. min_rec_mask的属性,在存储目录项记录的页中的主键值最小的目录项记录的min_rec_mask值为1,其他别的记录的min_rec_mask值都是0。

现在假设一个存储目录项记录的页最多只能存放4条目录项记录(注意是假设)。随着数据量的增多,一个中存不下所有的目录项记录,这时候就要申请新的。同时,为了快速定位到数据所在的目录项,需要生成一个更高级的目录
请添加图片描述

以上图形就是InnoDB组织组织数据的的形式,或者说是一种数据结构,叫做B+树

存放用户记录的数据页跟存放目录项记录的数据页,都存放到B+树这个数据结构中,这些数据页也被称为节点。从图中可以看出来,用户记录其实都存放在B+树的最底层的节点上,这些节点也被称为叶子节点叶节点,用来存放目录项的节点称为非叶子节点或者内节点,其中B+树最上面的那个节点也称为根节点

3.2 先有根节点再有叶子节点

上面介绍B+树索引结构时,为了方便理解,是先画出了存储用户记录的叶子节点,然后再画出存储目录项记录的内节点,实际上B+树的形成过程如下:

  • 一张表中,每创建一个B+树索引(聚簇索引不是人为创建的,默认就有),都会为这个索引创建一个根节点页面。最开始表中没有数据,每个B+树索引对应的根节点中既没有用户记录,也没有目录项记录。

  • 随后向表中插入用户记录时,先把用户记录存储到这个根节点中。

  • 当根节点中的可用空间用完时继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如页a中,然后对这个新页进行页分裂的操作,得到另一个新页,比如页b。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到页a或者页b中,而根节点便升级为存储目录项记录的页。

需要特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建立一个索引,那么它的根节点的页号便会被记录到某个地方,然后凡是InnoDB存储引擎需要用到这个索引的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引。

3.3 一条记录在索引中的查找过程

现在有一条语句:

select * from index_demo where c1 = 5;

回到我们最开始的问题,一条记录在 B+树中是如何进行查找的?只要分为两大步骤:

  1. 找到该条记录所在的
    • 从根节点出发,通过c1的值跟根页面中的目录项中的主键值进行比较,确认下一层级内节点的页号
    • 在内节点中再次比较主键值
    • 重复步骤2,直到找到叶子节点
  2. 在叶子节点中找到该条记录
    • 通过二分法确定该记录所在的槽。
    • 通过记录的next_record属性遍历该槽所在的组中的各个记录。

假设所有存放用户记录的叶子节点数据页可以存放100条用户记录,存放目录项记录的内节点的数据页可以存放1000条目录项记录,那么:

  • 如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放100条记录。
  • 如果B+树有2层,最多能存放1000×100=100000条记录。
  • 如果B+树有3层,最多能存放1000×1000×100=100000000条记录。
  • 如果B+树有4层,最多能存放1000×1000×1000×100=100000000000条记录。

所以一般情况下,实际开发中用到的B+树都不会超过4层,那么通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页)。

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

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

相关文章

(c语言进阶)结构体内存对齐和修改默认对齐数

一.结构体内存对齐 结构体内存大小计算方法&#xff1a; 偏移量&#xff1a;是指某个成员在结构体中相对于结构体首地址的偏移字节数。在计算机中&#xff0c;结构体是一种自定义数据类型&#xff0c;它由多个不同类型的成员组成。每个成员在内存中的存储位置是连续的&#xf…

短波红外相机的原理及应用场景

短波红外 (简称SWIR&#xff0c;通常指0.9~1.7μm波长的光线) 是一种比可见光波长更长的光。这些光不能通过“肉眼”看到&#xff0c;也不能用“普通相机”检测到。由于被检测物体的材料特性&#xff0c;一些在可见光下无法看到的特性&#xff0c;却能在近红外光下呈现出来&…

231203 刷题日报

周天&#xff0c;阳光明媚&#xff0c;期待一切顺利。 上午回顾了昨天刷的题&#xff1a; 快排、十字链表、两数组公共元素 下午看子序列&#xff1a; 300. 最长递增子序列 53. 最大子数组和 这两个题对比&#xff0c;子序列因为有“递增”限制&#xff0c;且不连续&#…

Vue项目解决van-calendar 打开下拉框显示空白(白色),需滑动一下屏幕,才可正常显示

问题描述&#xff0c;如图 ipad(平板&#xff09;或者 H5移动端引入Vant组件的日历组件&#xff08;van-calendar&#xff09;&#xff0c;初始化显示空白&#xff0c;需滚动一下屏幕&#xff0c;才可正常显示 解决方法 需在van-calendar上绑定open"openCalendar"事件…

SAP GRID-ALV复选框+GRID事件

实现功能: 复选框\设置复选框是否可编辑\实现changed_finished事件. 一、ALV增加复选框&#xff1a; 1.1、在输出内表里增加一个SEL的字段&#xff1a; sel TYPE c, 1.2、在build_fieldcat FORM里设置checkbox属性和edit属性&#xff0c;并输出SEL字段&#xff1a;…

机器人制作开源方案 | 校园餐具回收分类机器人

作者&#xff1a;梁桥、吴振宇、凌福海、李清轩、姜晓敏 单位&#xff1a;华北科技学院 指导老师&#xff1a;韩红利、张伟杰 1. 场景调研 1.1 项目实施目的 受新冠病毒引起的影响&#xff0c;人们生产生活发生了巨大的改变。现处于疫情防控常态化阶段&#xff0c;为应对点状…

ElementUI+vue+nodejs培训学校课程预约网站的设计与开发

该系统将采用B/S结构模式&#xff0c;前端部分主要使用html、css、JavaScript等技术&#xff0c;使用Vue和ElementUI框架搭建前端页面&#xff0c;后端部分将使用Nodejs来搭建服务器&#xff0c;并使用MySQL建立后台数据系统&#xff0c;通过axios完成前后端的交互&#xff0c;…

题目:神奇的进制

解题思路&#xff1a; 用电脑自带的计算器&#xff0c;切换到程序员模式。里面有进制转换功能。 由题目&#xff0c;要求严格递增且都为字母&#xff0c;还要大于2023&#xff0c;则数字16进制为ABC。

❀My学习Linux命令小记录(10)❀

目录 ❀My学习Linux命令小记录&#xff08;10&#xff09;❀ 36.fold指令 37.expr指令 38.iperf指令 39.telnet指令 40.ssh指令 ❀My学习Linux命令小记录&#xff08;10&#xff09;❀ 36.fold指令 功能说明&#xff1a;控制文件内容输出时所占用的屏幕宽度&#xff0c…

智能优化算法应用:基于供需算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于供需算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于供需算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.供需算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

基于PHP的在线日语学习平台

有需要请加文章底部Q哦 可远程调试 PHP在线日语学习平台 一 介绍 此日语学习平台基于原生PHP开发&#xff0c;数据库mysql。系统角色分为用户和管理员。(附带参考设计文档) 技术栈&#xff1a;phpmysqlphpstudyvscode 二 功能 学生 1 注册/登录/注销 2 个人中心 3 查看课程…

善网商城上线洁柔产品 公益人专享爱心价官方正品

近日&#xff0c;中国善网慈善商城&#xff08;以下简称善网商城&#xff09;系统经升级后重新上线。目前善网商城线上销售的中顺洁柔旗下慈善产品已顺利获得中顺洁柔纸业股份有限公司授权&#xff0c;双方就合作事宜达成共识&#xff0c;并于近日签订线上经营授权书。 &#x…

Optional源码分析(涉及Objects源码和Stream源码)

研究Optional源码之前先谈一谈Objects源码。 主要代码&#xff1a; ForceInlinepublic static <T> T requireNonNull(T obj) {if (obj null) {throw new NullPointerException();} else {return obj;}}ForceInlinepublic static <T> T requireNonNull(T obj, Str…

C/C++,图算法——凸包的快速壳(Quick Hull)算法的源代码

1 文本格式 // C program to implement Quick Hull algorithm // to find convex hull. #include<bits/stdc.h> using namespace std; // iPair is integer pairs #define iPair pair<int, int> // Stores the result (points of convex hull) set<iPair>…

外包干了2个月,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

Linux --- 进程控制

目录 1. 进程创建 1.1. 内核数据结构的处理 1.2. 代码的处理 1.3. 数据的处理&#xff1a; 方案一&#xff1a;fork创建子进程的时候&#xff0c;直接对数据进行拷贝处理&#xff0c;让父子进程各自私有一份 方案二&#xff1a;写实拷贝(copy on write) 1.4. fork常规用…

RocketMQ-整合SpringBoot

SpringBoot整合RocketMQ 创建Maven工程&#xff0c;引入关键依赖&#xff1a; <dependencies><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.2</ver…

flutter开发实战-readmore长文本展开和收缩控件

flutter开发实战-readmore长文本展开和收缩控件 当长文本展开和收缩控件&#xff0c;我们需要使用readmore来处理长文本展开和收缩&#xff0c;方便阅读 一、引入readmore 在工程的pubspec.yaml中引入插件 readmore: ^2.1.0ReadMoreText的属性如下 const ReadMoreText(this.…

Pandas操作数据库

一&#xff1a;Pandas读取数据库数据 二&#xff1a;Pandas读取海量数据 三&#xff1a;Pandas向数据库存数据 四&#xff1a;Pandas写入海量数据

低噪声,带内置 ALC 回路的双通道均衡放大器,应用于立体声收录机和盒式录音机的芯片D3308的描述

D3308 是一块带有 ALC 的双通道前置放大器。它适用于立体声收录机和盒式录音机。采用 SIP9、SOP14 的封装形式封装。 主要特点 带内置 ALC 回路的双通道均衡放大器 低噪声: VNIl.OuV(典型值)。开环电压增益高: 80dB (典型值)工作电源电压范围宽: 通道间的…