首页 > 数据库 >【MySQL】从数据页的角度看 B+ 树

【MySQL】从数据页的角度看 B+ 树

时间:2023-05-22 15:57:36浏览次数:27  
标签:记录 索引 键值 MySQL 角度看 InnoDB 数据 节点

1  前言

我们都知道 MySQL 里 InnoDB 存储引擎是采用 B+ 树来组织数据的。但是大家知道 B+ 树里的节点里存放的是什么呢?查询数据的过程又是怎样的?那么这节我们从数据页的角度看 B+ 树,看看每个节点长啥样。

2  InnoDB 是如何存储数据的?

MySQL 支持多种存储引擎,不同的存储引擎,存储数据的方式也是不同的,我们最常使用的是 InnoDB 存储引擎,所以就跟大家图解下InnoDB 是如何存储数据的。

记录是按照行来存储的,但是数据库的读取并不以「行」为单位,否则一次读取(也就是一次 I/O 操作)只能处理一行数据,效率会非常低。

因此,InnoDB 的数据是按「数据页」为单位来读写的,也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。

数据库的 I/O 操作的最小单位是页,InnoDB 数据页的默认大小是 16KB,意味着数据库每次读写都是以 16KB 为单位的,一次最少从磁盘中读取 16K 的内容到内存中,一次最少把内存中的 16K 内容刷新到磁盘中。

数据页包括七个部分,结构如下图:

这 7 个部分的作用如下图:

在 File Header 中有两个指针,分别指向上一个数据页和下一个数据页,连接起来的页相当于一个双向的链表,如下图所示:

采用链表的结构是让数据页之间不需要是物理上的连续的,而是逻辑上的连续。

数据页的主要作用是存储记录,也就是数据库的数据,所以重点说一下数据页中的 User Records 是怎么组织数据的。

数据页中的记录按照「主键」顺序组成单向链表,单向链表的特点就是插入、删除非常方便,但是检索效率不高,最差的情况下需要遍历链表上的所有节点才能完成检索。

因此,数据页中有一个页目录,起到记录的索引作用,就像我们书那样,针对书中内容的每个章节设立了一个目录,想看某个章节的时候,可以查看目录,快速找到对应的章节的页数,而数据页中的页目录就是为了能快速找到记录。

那 InnoDB 是如何给记录创建页目录的呢?页目录与记录的关系如下图:

页目录创建的过程如下:

  1. 将所有的记录划分成几个组,这些记录包括最小记录和最大记录,但不包括标记为“已删除”的记录;

  2. 每个记录组的最后一条记录就是组内最大的那条记录,并且最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段(上图中粉红色字段)

  3. 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录。

从图可以看到,页目录就是由多个槽组成的,槽相当于分组记录的索引。然后,因为记录是按照「主键值」从小到大排序的,所以我们通过槽查找记录时,可以使用二分法快速定位要查询的记录在哪个槽(哪个记录分组),定位到槽后,再遍历槽内的所有记录,找到对应的记录,无需从最小记录开始遍历整个页中的记录链表。

以上面那张图举个例子,5 个槽的编号分别为 0,1,2,3,4,我想查找主键为 11 的用户记录:

  • 先二分得出槽中间位是 (0+4)/2=2 ,2号槽里最大的记录为 8。因为 11 > 8,所以需要从 2 号槽后继续搜索记录;

  • 再使用二分搜索出 2 号和 4 槽的中间位是 (2+4)/2= 3,3 号槽里最大的记录为 12。因为 11 < 12,所以主键为 11 的记录在 3 号槽里;

  • 再从 3 号槽指向的主键值为 9 记录开始向下搜索 2 次,定位到主键为 11 的记录,取出该条记录的信息即为我们想要查找的内容。

看到第三步的时候,可能有的同学会疑问,如果某个槽内的记录很多,然后因为记录都是单向链表串起来的,那这样在槽内查找某个记录的时间复杂度不就是 O(n) 了吗?

这点不用担心,InnoDB 对每个分组中的记录条数都是有规定的,槽内的记录就只有几条:

  • 第一个分组中的记录只能有 1 条记录;

  • 最后一个分组中的记录条数范围只能在 1-8 条之间;

  • 剩下的分组中记录条数范围只能在 4-8 条之间。

3  B+ 树是如何进行查询的?

上面我们都是在说一个数据页中的记录检索,因为一个数据页中的记录是有限的,且主键值是有序的,所以通过对所有记录进行分组,然后将组号(槽号)存储到页目录,使其起到索引作用,通过二分查找的方法快速检索到记录在哪个分组,来降低检索的时间复杂度。

但是,当我们需要存储大量的记录时,就需要多个数据页,这时我们就需要考虑如何建立合适的索引,才能方便定位记录所在的页。

为了解决这个问题,InnoDB 采用了 B+ 树作为索引。磁盘的 I/O 操作次数对索引的使用效率至关重要,因此在构造索引的时候,我们更倾向于采用“矮胖”的 B+ 树数据结构,这样所需要进行的磁盘 I/O 次数更少,而且 B+ 树 更适合进行关键字的范围查询。

InnoDB 里的 B+ 树中的每个节点都是一个数据页,结构示意图如下:

通过上图,我们看出  B+ 树的特点:

  • 只有叶子节点(最底层的节点)才存放了数据,非叶子节点(其他上层节)仅用来存放目录项作为索引。

  • 非叶子节点分为不同层次,通过分层来降低每一层的搜索量;

  • 所有节点按照索引键大小排序,构成一个双向链表,便于范围查询;

我们再看看 B+ 树如何实现快速查找主键为 6 的记录,以上图为例子:

  • 从根节点开始,通过二分法快速定位到符合页内范围包含查询值的页,因为查询的主键值为 6,在[1, 7)范围之间,所以到页 30 中查找更详细的目录项;

  • 在非叶子节点(页30)中,继续通过二分法快速定位到符合页内范围包含查询值的页,主键值大于 5,所以就到叶子节点(页16)查找记录;

  • 接着,在叶子节点(页16)中,通过槽查找记录时,使用二分法快速定位要查询的记录在哪个槽(哪个记录分组),定位到槽后,再遍历槽内的所有记录,找到主键为 6 的记录。

可以看到,在定位记录所在哪一个页时,也是通过二分法快速定位到包含该记录的页。定位到该页后,又会在该页内进行二分法快速定位记录所在的分组(槽号),最后在分组内进行遍历查找。

4  聚集索引和二级索引

索引又可以分成聚集索引和非聚集索引(二级索引),它们区别就在于叶子节点存放的是什么数据:

  • 聚集索引的叶子节点存放的是实际数据,所有完整的用户记录都存放在聚集索引的叶子节点;

  • 二级索引的叶子节点存放的是主键值,而不是实际数据。

因为表的数据都是存放在聚集索引的叶子节点里,所以 InnoDB 存储引擎一定会为表创建一个聚集索引,且由于数据在物理上只会保存一份,所以聚簇索引只能有一个。

InnoDB 在创建聚簇索引时,会根据不同的场景选择不同的列作为索引:

  • 如果有主键,默认会使用主键作为聚簇索引的索引键;

  • 如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键;

  • 在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键;

一张表只能有一个聚簇索引,那为了实现非主键字段的快速搜索,就引出了二级索引(非聚簇索引/辅助索引),它也是利用了 B+ 树的数据结构,但是二级索引的叶子节点存放的是主键值,不是实际数据。

二级索引的 B+ 树如下图,数据部分为主键值:

因此,如果某个查询语句使用了二级索引,但是查询的数据不是主键值,这时在二级索引找到主键值后,需要去聚簇索引中获得数据行,这个过程就叫作「回表」,也就是说要查两个 B+ 树才能查到数据。不过,当查询的数据是主键值时,因为只在二级索引就能查询到,不用再去聚簇索引查,这个过程就叫作「索引覆盖」,也就是只需要查一个 B+ 树就能找到数据。

5  小结

InnoDB 的数据是按「数据页」为单位来读写的,默认数据页大小为 16 KB。每个数据页之间通过双向链表的形式组织起来,物理上不连续,但是逻辑上连续。

数据页内包含用户记录,每个记录之间用单项链表的方式组织起来,为了加快在数据页内高效查询记录,设计了一个页目录,页目录存储各个槽(分组),且主键值是有序的,于是可以通过二分查找法的方式进行检索从而提高效率。

为了高效查询记录所在的数据页,InnoDB 采用 B+ 树作为索引,每个节点都是一个数据页。

如果叶子节点存储的是实际数据的就是聚簇索引,一个表只能有一个聚簇索引;如果叶子节点存储的不是实际数据,而是主键值则就是二级索引,一个表中可以有多个二级索引。

在使用二级索引进行查找数据时,如果查询的数据能在二级索引找到,那么就是「索引覆盖」操作,如果查询的数据不在二级索引里,就需要先在二级索引找到主键值,需要去聚簇索引中获得数据行,这个过程就叫作「回表」。

好啦,关于数据页的我们就看到这里哈,有理解不对的地方欢迎指正哈。

标签:记录,索引,键值,MySQL,角度看,InnoDB,数据,节点
From: https://www.cnblogs.com/kukuxjx/p/17420789.html

相关文章

  • 读取数据库JSON格式数据信息处理办法记录
    遇到的问题:现有代码如下defListQuery(self):sql01="SELECTcontentFROMzt_user_customdata\WHERErealname='alarm-server'ANDaccount='alarm-server'"result01=self.CommonQueryFunc(sql01)result02=str(resu......
  • 第四十天 各种各样的mysql数据查询方法
    一、昨日内容回顾约束条件之主键primarykey1.InnoDB规定表必须有且只有一个主键(单列主键联合主键)idintprimarykey单例主键idint,uidint,primarykey(id,uid)联合主键idintprimarykeyauto_increment主键自增2.如果表中有主键那么基于主键查询数据速度会非......
  • 二:用电信号传输TCP/IP数据-3.2-ACK号的管理
    上一节讲了数据收发的大概过程,实际上网络的错误检测和补偿机制非常复杂,这一节讲三个关键点。一、返回ACK号的等待时间返回ACK号的等待时间叫超时时间。当网络传输繁忙时ACK号的返回会变慢,这时就要将等待时间设置得长一点,不然可能已经重传了,ACK号才到达。这样的重传是多余的,虽然......
  • 10g中如何修改数据库字符集?
    SQL>!uname-aLinuxroger2.6.9-42.ELsmp#1SMPWedJul1223:27:17EDT2006i686i686i386GNU/LinuxSQL>selectuserenv('language')fromdual;USERENV('LANGUAGE')----------------------------------------------------AMER......
  • MySql8修改root密码,修改用户名
    usemysql;updateusersetauthentication_string=''whereuser='root';//root设为空ALTERuser'root'@'localhost'IDENTIFIEDBY'root';//root密码设为rootflushprivileges;//刷新权限 usemysql;......
  • 16种常用的数据分析方法汇总(转载)
    一、描述统计描述性统计是指运用制表和分类,图形以及计筠概括性数据来描述数据的集中趋势、离散趋势、偏度、峰度。缺失值填充:常用方法:剔除法、均值法、最小邻居法、比率回归法、决策树法。正态性检验:很多统计方法都要求数值服从或近似服从正态分布,所以之前需要进行正态性检验......
  • 第三回:数据何所依,硬盘话原理
    公众号原文前情回顾:《第一回:天才闯秘境,绝地寻生机》上回说到,阿飞从混沌中醒来,意外发现自己的大脑被数据化存储到了一台计算机内存中,根据神秘声音的指示,他需要赶紧联系网卡找到自己大脑的另一半然后逃离这里。不料内存却告诉他,如果不赶紧把自己持久化存储起来,一旦计算机关闭他就会......
  • 若依框架当参数为Map集合时数据权限的设置
    1、controller接口参数类型@PreAuthorize("@ss.hasPermi('manual:staff:list')")@GetMapping("/list")publicTableDataInfolist(@RequestParamMap<String,Object>map){map.put("params",newParmStaffCostManu......
  • 空间数据库-msyql空间数据大纲
    MySql支持的类型点POINT(1520) 线LINESTRING(00,1010,2025,5060) 面POLYGON((00,100,1010,010,00),(55,75,77,57,55)) 多个点MULTIPOINT(00,2020,6060) 多个线MULTILINESTRING((1010,2020),(1515,3015)) 多个面MULTI......
  • MySQL常用关键字和函数及部分关键字使用场景
    世间情动,不过盛夏白瓷梅子汤,碎冰碰壁当啷响。一,关键字使用顺序在使用SQL查询时,关键字的顺序并不是非常重要,SQL解释器可以根据查询的语法结构自动推断其执行顺序。但是,为了使查询更加易读,并且能够避免出现在结果中无法预期的重复数据,建议始终按照以下顺序使用关键字:1,SEL......