B+树 和 跳表 的结构及区别,不同的用途【mysql的索引为什么使用B+树而不使用跳表?】

导语:
详解B+树与跳表的结构及区别,描述B+树与跳表新增数据的过程,解释MySQL与Redis选择对应结构的原因。

mysql数据表里直接遍历这一行行数据,性能就是O(n),比较慢。为了加速查询,使用了B+树来做索引,将查询性能优化到了O(lg(n))

但问题就来了,查询数据性能在 lg(n) 级别的数据结构有很多,比如redis的zset里用到的跳表,也是lg(n),并且实现还贼简单。
那为什么mysql的索引,不使用跳表呢?

1. B+树的结构

一般B+树是由多个页组成的多层级结构,每个页16Kb,对于主键索引来说,最末级的叶子结点放行数据非叶子结点放的则是索引信息(主键id和页号),用于加速查询。看下B+树的结构,如下图:
在这里插入图片描述
比如说我们想要查找行数据5。会先从顶层页的record们入手。record里包含了主键id和页号(页地址)。关注黄色的箭头,向左最小id是1,向右最小id是7。那id=5的数据如果存在,那必定在左边箭头。于是顺着的record的页地址就到了6号数据页里,再判断id=5>4,所以肯定在右边的数据页里,于是加载105号数据页。

在105号数据页里,虽然有多行数据,但也不是挨个遍历的,数据页内还有个页目录的信息,它可以通过二分查找的方式加速查询行数据,于是找到id=5的数据行,完成查询。

从上面可以看出,B+树利用了空间换时间的方式(构造了一批非叶子结点用于存放索引信息),将查询时间复杂度从O(n)优化为O(lg(n))

2. 跳表的结构

接下来看一下跳表的结构。

为了存储一行行的数据。可以将它们用链表串起来。如下图:

在这里插入图片描述

如果想要查询链表中的其中一个结点,时间复杂度是O(n),这谁顶得住,于是将部分链表结点提出来,再构建出一个新的链表。

在这里插入图片描述

这样当想要查询一个数据的时候,我先查上层的链表,就很容易知道数据落在哪个范围,然后跳到下一个层级里进行查询。这样就把搜索范围一下子缩小了一大半。

比如查询id=10的数据,我们先在上层遍历,依次判断1,6,12,很快就可以判断出10在6到12之间,然后往下一跳,就可以在遍历6,7,8,9,10之后,确定id=10的位置。直接将查询范围从原来的1到10,变成现在的1,6,7,8,9,10,算是砍半了。

在这里插入图片描述
既然两层链表就直接将查询范围砍半了,那多加几层,岂不妙哉?

于是跳表就这样变成了多层。

在这里插入图片描述

如果还是查询id=10的数据,就只需要查询1,6,9,10就能找到,比两层的时候更快一些。

在这里插入图片描述

所以,跳表也是通过牺牲空间换取时间的方式提升查询性能。时间复杂度都是lg(n)。

3. B+树与跳表的区别

从上面结果可以看到,B+树和跳表的最下面一层,都包含了所有的数据,且都是顺序的,适合用于范围查询。往上的层级都是构建出来用于提升搜索性能的。这两者实在是太像了。但他们两者在新增和删除数据时,还是有些区别的。下面以新增数据为例解释一下。

3.1 B+树新增数据会如何?

B+树本质上是一种多叉平衡二叉树。关键在于"平衡"这两个字,对于多叉树结构来说,它的含义是子树们的高度层级尽量一致(一般最多差一个层级),这样在搜索的时候,不管是到哪个子树分支,搜索次数都差不了太多。

当数据库表不断插入新的数据时,为了维持B+树的平衡,B+树会不断分裂调整数据页。

B+树分为叶子结点和非叶子结点。当插入一条数据时,叶子结点和它上层的索引结点(非叶子结点)最大容量都是16k,它们都有可能会满。

加入一条数据,根据数据页会不会满,分为三种情况:

  • 叶子结点和索引结点都没满:这种情况最简单,直接插入到叶子结点中即可

在这里插入图片描述

  • 叶子结点满了,但索引结点没满:此时需要拆分叶子结点,同时索引结点要增加新的索引信息。
    在这里插入图片描述
  • 叶子结点满了,且索引结点也满了:叶子和索引结点都要拆分,同时往上还要再加一层索引。
    在这里插入图片描述

从上面可以看到,只有在叶子和索引结点都满了的情况下,B+树才会考虑加入一层新的结点。

如果,把三层B+树塞满,那大概需要2kw左右的数据。

3.2 跳表新增数据会如何?

跳表同样也是很多层,新增一个数据时,最底层的链表需要插入数据。此时,是否需要在上面的几层中加入数据做索引呢?这个就纯靠随机函数了。

理论上为了达到二分的效果,每一层的结点数需要是下一层结点数的二分之一。也就是说现在有一个新的数据插入了,它有50%的概率需要在第二层加入索引,有25%的概率需要在第三层加个索引,以此类推,直到最顶层

举个例子:如果跳表中插入数据id=6,且随机函数返回第三层(有25%的概率),那就需要在跳表的最底层到第三层都插入数据。

在这里插入图片描述
如果这个随机函数设计成上面这样,当数据量样本足够大的时候,数据的分布就符合我们理想中的"二分"。

跟上面B+树不一样,跳表是否新增层数,纯粹靠随机函数,根本不关心前后上下结点

4.mysql的索引为什么使用B+树而不使用跳表?

B+树是多叉树结构,每个结点都是一个16k的数据页,能存放较多索引信息,所以扇出很高。三层左右就可以存储2kw左右的数据。也就是说查询一次数据,如果这些数据页都在磁盘里,那么最多需要查询三次磁盘IO

跳表是链表结构,一条数据一个结点,如果最底层要存放2kw数据,且每次查询都要能达到二分查找的效果,2kw大概在2的24次方左右,所以,跳表大概高度在24层左右。最坏情况下,这24层数据会分散在不同的数据页里,也即是查一次数据会经历24次磁盘IO

因此存放同样量级的数据,B+树的高度比跳表的要少,如果放在mysql数据库上来说,就是磁盘IO次数更少,因此B+树查询更快

而针对写操作,B+树需要拆分合并索引数据页,跳表则独立插入,并根据随机函数确定层数,没有旋转和维持平衡的开销,因此跳表的写入性能会比B+树要好

其实,mysql的存储引擎是可以换的,以前是myisam,后来才有的innodb,它们底层索引用的都是B+树。也就是说,你完全可以造一个索引为跳表的存储引擎装到mysql里。事实上,facebook造了个rocksDB的存储引擎,里面就用了跳表。直接说结论,它的写入性能确实是比innodb要好,但读性能确实比innodb要差不少。

5. redis为什么使用跳表而不使用B+树或二叉树呢?

redis支持多种数据结构,里面有个有序集合,也叫ZSET。内部实现就是跳表。那为什么要用跳表而不用B+树等结构呢?

这个几乎每次面试都要被问一下。(虽然已经很熟了,但每次都要装作之前没想过,现场思考一下才知道答案。真的,很考验演技。)

大家知道,redis 是纯纯的内存数据库。进行读写数据都是操作内存,跟磁盘没啥关系,因此也不存在磁盘IO了,所以层高就不再是跳表的劣势了。

并且前面也提到B+树是有一系列合并拆分操作的,换成红黑树或者其他AVL树的话也是各种旋转,目的也是为了保持树的平衡
而跳表插入数据时,只需要随机一下,就知道自己要不要往上加索引,根本不用考虑前后结点的感受,也就少了旋转平衡的开销

因此,redis选了跳表,而不是B+树。

总结

  • B+树是多叉平衡搜索树,扇出高,只需要3层左右就能存放2kw左右的数据,同样情况下跳表则需要24层左右,假设层高对应磁盘IO,那么B+树的读性能会比跳表要好,因此mysql选了B+树做索引
  • redis的读写全在内存里进行操作,不涉及磁盘IO,同时跳表实现简单,相比B+树、AVL树、少了旋转树结构的开销,因此redis使用跳表来实现ZSET,而不是树结构。
  • 存储引擎RocksDB内部使用了跳表,对比使用B+树的innodb,虽然写性能更好,但读性能属实差了些。在读多写少的场景下,B+树依旧很强。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML5 Web Worker之性能优化

描述 由于 JavaScript 是单线程的,当执行比较耗时的任务时,就会阻塞主线程并导致页面无法响应,这就是 Web Workers 发挥作用的地方。它允许在一个单独的线程(称为工作线程)中执行耗时的任务。这使得 JavaScript 代码可…

第三周组会——动态多目标优化算法

首先对上周写的DF测试函数进行了优化和增加 DF4 pf: DF5测试函数PF DF6 遇到的问题,在算法问题的参数taut(变化频率)默认是10数字变小时就算是9,算法会跟不上收敛 新读的文献 A Novel Dynamic Multiobjective Optimization Algorithm With Hierarchical Response System 一…

1.2_3 TCP/IP参考模型

文章目录 1.2_3 TCP/IP参考模型(一)OSI参考模型与TCP/IP参考模型(二)5层参考模型(三)5层参考模型的数据封装与解封装 1.2_3 TCP/IP参考模型 (一)OSI参考模型与TCP/IP参考模型 TCP/I…

【理解指针(1)】

理解指针(1) 1什么是内存2指针变量和地址21 取地址操作符(&)22 指针变量23 解引用操作符(*)24 指针变量的大小 3指针变量的意义31指针的解引用32 指针加减整数33 void* 指针 4. const 修饰指针41 const…

递归搜索回溯相关的题目解析和练习2

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;下面的题目用的方法和前面几篇的方法是一样的&#xff0c;写起来是比较困难的&#xff0c;加油 字母大小写全排列 https://leetcode.cn/problems/letter-case-permutation/ 解析 代码 class Solution {vector<string&g…

【Docker】容器的概念

容器技术&#xff1a;容器技术是基于虚拟化技术的&#xff0c;它使应用程序从一个计算机环境快速可靠地转移到另一个计算机环境中&#xff0c;可以说是一个新型地虚拟化技术。 一、docker容器 Docker:是一个开源地容器引擎Docker 是一种轻量级的容器化技术&#xff0c;其主要原…

分割模型TransNetR的pytorch代码学习笔记

这个模型在U-net的基础上融合了Transformer模块和残差网络的原理。 论文地址&#xff1a;https://arxiv.org/pdf/2303.07428.pdf 具体的网络结构如下&#xff1a; 网络的原理还是比较简单的&#xff0c; 编码分支用的是预训练的resnet模块&#xff0c;解码分支则重新设计了。…

PyTorch搭建LeNet训练集详细实现

一、下载训练集 导包 import torch import torchvision import torch.nn as nn from model import LeNet import torch.optim as optim import torchvision.transforms as transforms import matplotlib.pyplot as plt import numpy as npToTensor()函数&#xff1a; 把图像…

git学习(创建项目提交代码)

操作步骤如下 git init //初始化git remote add origin https://gitee.com/aydvvs.git //建立连接git remote -v //查看git add . //添加到暂存区git push 返送到暂存区git status // 查看提交代码git commit -m初次提交git push -u origin "master"//提交远程分支 …

微信小程序(五十二)开屏页面效果

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.使用控件模拟开屏界面 2.倒计时逻辑 3.布局方法 4.TabBar隐藏复现 源码&#xff1a; components/openPage/openPage.wxml <view class"openPage-box"><image src"{{imagePath}}"…

三维不同坐标系下点位姿态旋转平移变换

文章目录 前言正文计算方法思路Python实现总结前言 本文主要说明以下几种场景3D变换的应用: 3D相机坐标系下长方体物体,有本身坐标系,沿该物体长边方向移动一段距离,并绕长边轴正旋转方向转90度,求解当前物体中心点在相机坐标系下的位置和姿态多关节机器人末端沿工具坐标…

STM32 利用FlashDB库实现在线扇区数据管理不丢失

STM32 利用FlashDB库实现在线扇区数据管理不丢失 &#x1f4cd;FalshDB地址:https://gitee.com/Armink/FlashDB ✨STM32没有片内EEPROM这样的存储区&#xff0c;虽然有备份寄存器&#xff0c;仅可以实现对少量数据的频繁存储&#xff0c;但是依赖备份电源&#xff08;BAT引脚&a…

美国签证|附面签相关事项√

小伙伴最近都忙着办签证吧&#xff01;但是需要注意的是&#xff0c;美国的签证跟其他任何国家的签证不同&#xff0c;并不是办理了就一定拿得到&#xff0c;据说概率是50%左右。所以办理美国签证&#xff0c;不要太着急啦&#xff01;先来了解一下美国签证的相片该怎么拍叭 ✅…

RocketMQ的事务消息流程

什么是事务消息&#xff1f; 事务消息是一种在发送方和接收方之间保证消息传递的一致性和可靠性的消息传递机制。在消息发送过程中&#xff0c;生产者可以将消息发送到消息队列&#xff0c;但不会立即被消费者接收和处理。相反&#xff0c;消息会先进入一种“准备”状态&#x…

用chatgpt写insar地质灾害的论文,重复率只有1.8%,chatgpt4.0写论文不是梦

突发奇想&#xff0c;想用chatgpt写一篇论文&#xff0c;并看看查重率&#xff0c;结果很惊艳&#xff0c;说明是确实可行的&#xff0c;请看下图。 下面是完整的文字内容。 InSAR (Interferometric Synthetic Aperture Radar) 地质灾害监测技术是一种基于合成孔径雷达…

【JavaScript】JavaScript 变量 ① ( JavaScript 变量概念 | 变量声明 | 变量类型 | 变量初始化 | ES6 简介 )

文章目录 一、JavaScript 变量1、变量概念2、变量声明3、ES6 简介4、变量类型5、变量初始化 二、JavaScript 变量示例1、代码示例2、展示效果 一、JavaScript 变量 1、变量概念 JavaScript 变量 是用于 存储数据 的 容器 , 通过 变量名称 , 可以 获取 / 修改 变量 中的数据 ; …

第十五届蓝桥杯模拟赛(第三期)

大家好&#xff0c;我是晴天学长&#xff0c;本次分享&#xff0c;制作不易&#xff0c;本次题解只用于学习用途&#xff0c;如果有考试需要的小伙伴请考完试再来看题解进行学习&#xff0c;需要的小伙伴可以点赞关注评论一波哦&#xff01;蓝桥杯省赛就要开始了&#xff0c;祝…

【DimPlot】【FeaturePlot】使用小tips

目录 DimPlot函数参数解析 栅格化点图 放大 ggplot2 图例的点&#xff0c;修改图例的标题 FeaturePlot函数参数解析 调整FeaturePlot颜色 分组绘制featureplot 随手笔记&#xff0c;持续更新中。。。 Reference DimPlot函数参数解析 object: 一个Seurat对象&#xff0c;…

工作纪实46-关于微服务的上线发布姿势

蓝绿部署 在部署时&#xff0c;不需要将旧版本的服务停掉&#xff0c;而是将新版本与旧版本同时运行&#xff0c;新版本测试无误之后再将旧版本停掉。这样可以避免再升级的过程中如果失败服务不可用的问题&#xff0c;因为同时部署了两个版本的程序&#xff0c;使得硬件资源是…

【项目笔记】java微服务:黑马头条(day01)

文章目录 环境搭建、SpringCloud微服务(注册发现、服务调用、网关)1)课程对比2)项目概述2.1)能让你收获什么2.2)项目课程大纲2.3)项目概述2.4)项目术语2.5)业务说明 3)技术栈4)nacos环境搭建4.1)虚拟机镜像准备4.2)nacos安装 5)初始工程搭建5.1)环境准备5.2)主体结构 6)登录6.1…