为什么MySQL使用B+树索引,而不使用其他作为索引呢?

索引介绍

索引是一种用于快速查询和检索数据的数据结构,其本质可以看成一种排序号的数据结构

索引的作用相当于书的目录。打个比方:在查字典的时候,如果没有目录,那我们就只能一页一页地去查,速度很慢。如果有目录,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。

索引底层数据结构存在很多种类型,常见的索引结构有:B树、B+树、Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam ,都使用了 B+树 作为索引结构。


索引的优缺点

优点:

  • 使用 索引 可以大大加快数据的检索速度(大大减少检索的数据量)
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性

缺点:

  • 创建索引和维护索引需要耗费很多时间。当对表中的数据进行增删改查的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
  • 索引需要使用物理文件存储,也会耗费一定空间

索引底层数据结构

Hash 表

哈希表是键值对的集合,通过即可快速取出对应的 ,因此哈希表可以快速地检索数据(接近O(1))。

#  构造散列函数
def hash(a):return (a % 8) ^ 7

类似的hash算法如上,通过异或或者移位的方式使得计算结果的规律性不那么明显,增加 hash 算法的可靠性

但是,哈希算法有一个 hash冲突 的问题,也就是说多个不同的 key(a) 最后得到的 index 可能相同。通常情况下,解决办法是 链地址法 ,即将哈希冲突的数据存放在链表中。

HashMap 就是一种常见的 采用 hash 表存储数据的结构(下图为jdk1.8之前的版本,jdk1.8之后的版本在此基础上加入了 红黑树 来解决链表过长带来的查询缓慢问题

既然 哈希表 这么快,为什么 MySQL 没有使用其作为索引的数据结构呢?

主要是因为 Hash 索引是无序的,不支持顺序和范围查询。假如我们需要对表中的数据进行排序或者范围查询的时候,Hash 索引就不可行了。并且每次 IO 只能取一个。

SELECT * FROM tb1 WHERE id < 500;

在这种范围查询中,直接遍历比500小的叶子节点就够了。而使用 Hash 索引是根据 hash 算法来定位的,不能确保 存储的顺序。


二叉查找树(BST)

二叉查找树 是一种基于二叉树的数据结构,有以下特点:

  1. 左子树的所有节点的值均小于根节点的值
  2. 右子树的所有节点的值均大于根节点的值
  3. 左右子树页分别为二叉查找树

当二叉查找树是平衡的时候,也就是树的每个节点的左右子树深度相差不超过 1 的时候,查询的时间复杂度为 O(log2(N)),具有比较高的效率。然而 ,当二叉查找树不平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也称为斜树),导致查询效率急剧下降,时间复杂度退化为 O(N)

二叉查找树的性能非常依赖于它的平衡度,这就导致其不适合作为 MySQL 底层索引的数据结构


AVL树 (平衡二叉树)

AVL树的特点是保证任何节点的左右子树高度之差不超过 1 ,因此也被称为高度平衡二叉树,它的查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。

AVL树采用了旋转操作来保持平衡。主要有四种旋转操作:LL旋转、RR旋转、LR旋转和RL旋转。其中LL旋转和RR旋转分别用于处理左左和右右失衡,而LR旋转 和 RL旋转 用于处理左右和右左失衡。

由于 AVL 树需要频繁地进行旋转操作来保持平衡,因此会有较大的计算开销进而降低了查询的性能。并且,在使用 AVL 树时,每个树节点仅存储一个数据,而每次进行磁盘 IO 时只能读取一个节点的数据,如果需要查询的数据分布在多个节点上,那么就需要进行多次磁盘 IO。磁盘IO是一项耗时的操作,在设计数据库索引时,我们需要优先考虑如何最大限度地减少磁盘 IO 操作的次数

除此之外,随着数据的增多,树的高度也会变高,这就意味着磁盘 IO 操作次数增多,会影响整体数据查询的效率。

比如,上图的平衡二叉树的高度为 5 ,那么在访问最底部的节点时,就需要 5 次 磁盘 IO 操作。根本原因是因为它们都是二叉树,也就是每个节点只能保存两个子节点


B 树

AVL 树虽然能保持查询操作的时间复杂度在 O(logn),但是因为它本质上是一个二叉树,每个节点只能存储 2 个子节点,那么当节点个数越多的时候,树的高度也会相应变高,这样就会增加磁盘的 IO次数,从而影响数据查询的效率。

为了解决降低树的高度的问题,就引出了 B 树,它不再限制一个节点只能有 2 个 子节点,而是允许 M 个子节点,从而降低 树的高度

假设 M = 3 ,那么就是一颗 3 阶的 B 树,特点就是每个节点最多有 2 个 (M-1个)数据和最多有 3 个(M 个)子节点,超过这些要求的话就会分裂节点:

 

假设我们在下面这幅 B 树中查找的索引值是 9 的记录

那么步骤可以分为以下几步:

  • 与根节点的索引(4,8)进行比较,9 > 8 所以查询最右边的子节点
  • 然后该子节点的索引为 (10,12),9 < 10 所以查询该节点的最左边的子节点
  • 找到索引值为 9 的节点。

可以看到 ,一颗 3 阶的 B 树在查询叶子节点中的数据时,由于树的高度是 3 ,所以在查询过程中会发生 3 次磁盘 IO 操作。

而同样的数据放在平衡二叉树下,树的高度就会很高,意味着磁盘 IO 操作会更多。所以 B 树在数据查询中比平衡二叉树的效率高

但是 B 树的每一个节点都包含数据(索引 + 记录) ,而用户的记录数据的大小很有可能远远超做过了索引数据,这就需要花费更多的 磁盘 IO 操作次数来读到 有用的索引数据。

而且在我们查询位于叶子节点上的数据的时候,其父节点以及之前的节点的数据也会加载到磁盘内存,但是这些数据时没有用的,我们只想获取最终节点上的数据,而不是查询过程中遍历过的节点的数据。所以使用 B 树作为索引的话会占用大量内存资源(这些资源本不应该被占用)

另外,如果使用 B 树来做范围查询的话,需要使用中序遍历,这会涉及到多个节点的磁盘 IO 问题一,从而导致整体速度下降。


B+树

B+树就是对 B 树做了一个升级,MySQL 中索引的数据结构就是采用的 B+树:

B+树和 B 树差异的点,主要是以下几个点:

  • 叶子节点才会存放实际数据,非叶子节点只能存放索引
  • 所有索引都会在叶子节点出现,叶子节点之间构成一个有序链表(在mysql中是双向链表)
  • 非叶子节点的索引会同时存在于子节点中,并且是在子节点中最大或者最小
  • 非叶子节点中有多少个子节点,就有多少个索引

查询效率:

B+树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储索引又存储记录的 B 树, B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更 [矮胖] ,查询底层节点的磁盘 IO 次数会更少

插入和删除效率:

B+ 树有大量的冗余节点,这导致删除一个节点的时候,可以直接从叶子节点中删除,甚至不需要动非叶子节点。

B+ 树的插入操作也是一样,由于有冗余节点,插入可能存在节点的分裂(节点饱和的话)但是最多只涉及树的一条路径。而且 B+树会自动平衡,不需要做更多复杂的操作

范围查询:

由于 B+树所有的叶子节点搜集通过链表进行连接,这种设计可以帮助我们更直接地获取范围内的数据,直接遍历链表即可,而不需要像B树那样,去遍历树。

在 Mysql 中 innoDB 存储引擎采用的就是 B+树作为索引的数据结构,但是在其基础上加了些改进:

  • InnoDB存储引擎中 叶子节点是通过双向链表进行连接的,这使得范围查询更加方便,既能向右遍历也能向左遍历
  • B+ 树的节点内容是数据页,数据页中存放了用户的记录以及各种信息,每个数据页的大小默认是 16KB


总结

二分查找树虽然是一个天然的二分结构,能很好的利用二分查找快速定位数据,但是它存在一种极端的情况,每当插入的元素都是树内最大的元素,就会导致二分查找树退化成一个链表,此时查询复杂度就会从 O(logn)降低为 O(n)。

了解决二分查找树退化成链表的问题,就出现了自平衡二叉树,保证了查询操作的时间复杂度就会一直维持在 O(logn) 。但是它本质上还是一个二叉树,每个节点只能有 2 个子节点,随着元素的增多,树的高度会越来越高。

而树的高度决定于磁盘 I/O 操作的次数,因为树是存储在磁盘中的,访问每个节点,都对应一次磁盘 I/O 操作,也就是说树的高度就等于每次查询数据时磁盘 IO 操作的次数,所以树的高度越高,就会影响查询性能

B 树和 B+ 都是通过多叉树的方式,会将树的高度变矮,所以这两个数据结构非常适合检索存于磁盘中的数据。

但是 MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,原因有:

  • B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
  • B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;
  • B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而 B 树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。

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

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

相关文章

从0到1,申请cos服务器并上传图片到cos文件服务器

目录 准备工作 Java代码编写 控制台打印 整理成工具类 编写接口 Postman测试 准备工作 1.进入网址腾讯云 产业智变云启未来 - 腾讯 (tencent.com) 2.搜索cos,点击立即使用&#xff0c;刚开始会免费赠送你 3.存储都是基于桶的&#xff0c;先创建桶&#xff0c;在桶里面创…

分类预测 | Matlab实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆神经网络的数据多输入分类预测

分类预测 | Matlab实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆神经网络的数据多输入分类预测 目录 分类预测 | Matlab实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆神经网络的数据多输入分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现WOA-BiLSTM鲸鱼算法…

虚拟机如何联网【NAT】

查看VMWARE的IP地址 #进入root用户 su -#更改虚拟网卡设置界面 vi /etc/sysconfig/network-scripts/ifcfg-ens33 修改ONBOOT为yes BOOTPROTO为static IPADDR为前面的网段 192.168.211.xx (xx为自己设置的&#xff0c;可以随意设置&#xff0c;前面的为前面查看的IP地址的前…

【ROS 2 基础-常用工具】-6 Rviz基础使用

所有内容请查看&#xff1a;博客学习目录_Howe_xixi的博客-CSDN博客

Chrome 115之后的版本,安装和使用chromedriver

在Python中使用selenium 时报如下错误&#xff1a; 1. 老版本chrome对应的chromedriver 下载地址&#xff1a;CNPM Binaries Mirror 2. 新版本chrome对应的chromedriver 下载地址&#xff1a;Chrome for Testing availability

一百九十一、Flume——Flume配置文件各参数含义(持续完善中)

一、目的 在实际项目的开发过程中&#xff0c;不同Kafka主题的数据规模、数据频率&#xff0c;需要配置不同的Flume参数&#xff0c;而这一切的调试、配置工作&#xff0c;都要建立在对Flume配置文件各参数含义的基础上 二、Flume各参数及其含义 &#xff08;一&#xff09;…

js获取视频编码

一.背景 有些浏览器不支持某些视频的编码方式导致播放出现问题&#xff0c;这个时候要限制视频上传 二.插件 https://unpkg.com/mediainfo.js0.1.4/dist/mediainfo.min.js 三.完整html代码 <!DOCTYPE html> <html lang"en"> <head><meta ch…

memcpy内存拷贝函数

目录 一、memcpy内存拷贝函数 注意事项 二、memcpy与strcpy对比 三、模拟实现memcpy函数 四、memcpy函数不能进行两块存在内存重叠的空间的内存拷贝 五、改进my_memcpy函数 一、memcpy内存拷贝函数 头文件&#xff1a;string.h 函数原型&#xff1a;void* memcpy(void* …

Element Plus el-form表单自定义插槽如何使用

//正常无插槽表单<el-form :model"form" label-width"120px"><el-form-item label"Activity name"><el-input v-model"form.name" /></el-form-item></el-form>//带插槽表单//适用二次封装的form组件&l…

.npmrc 使用详解

配置.npmrc之后需要&#xff1a; 清理项目目录中的 node _modules 目录(package-lock.json,umi)。清理 node cache: npm cache clear --force&#xff1b;{ 此步骤必须&#xff0c;主要是大家的电脑经过多年使用后&#xff0c;npm 配置比较混乱&#xff0c;为了避免或者减少配…

代码随想录算法训练营第五十三天 | 309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费

309.最佳买卖股票时机含冷冻期 视频讲解&#xff1a; https://programmercarl.com/0309.%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.html &#xff08;1&#xff09;代码 714.买卖股票的最佳时机含手续费…

【微信小程序】无纸化会议OA系统之首页搭建

前言 中国政府意识到信息技术的重要性&#xff0c;并开始积极推动信息产业的发展。一系列政策和措施被制定和执行&#xff0c;以促进信息技术的采用和普及&#xff0c;从而推动数字化时代的到来。为了响应国家推行的数字化时代&#xff0c;本篇文章以会议OA系统为背景进行编写…

unity脚本_碰撞检测函数 c#

在项目创建一个脚本文件包新建脚本Cor 将以下代码复制 using UnityEngine; public class Cor : MonoBehaviour{ #region 碰撞检测函数 #endregion //至少一个刚体和两个碰撞器让两个游戏物体产生碰撞 //物理材质Phy Material让两个游戏物体之间表现不同效果 //…

性能测试基础知识及性能指标

前言&#xff1a;最近公司接了个项目&#xff0c;领导开会突然来了句&#xff0c;让我出一份性能测试方案&#xff0c;后面性能测试工作交给我&#xff01;我心里想之前面试没要求会这个啊&#xff08;最少得加钱才能做吧~&#xff0c;没办法既然下达了指令&#xff0c;那就只能…

代码随想录二刷 Day42

62.不同路径 简单题目自己就可以写出来&#xff0c;注意下创建二维vector的方法就可以&#xff0c; dp table如下 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m,vector<int>(n,0));for (int i 0; i < n; i ) {dp[…

linux加密安全和时间同步

sudo实现授权 添加 vim /etc/sudoers luo ALL(root) /usr/bin/mount /deb/cdrom /mnt/ test ALL(root:ALL) ALL 在所有主机上 提权为root用户&#xff0c; 可以执行所有命令 户"test"被授权以"root"用户身份在任意主机上执行任意命令 切换luo用户使用 su…

协同过滤推荐算法UserCF、ItemCF

目录 相似度计算基于用户的协同过滤&#xff08;UserCF&#xff09;算法评估基于物品的协同过滤&#xff08;ItemCF&#xff09;协同过滤算法的权重改进协同过滤算法的问题分析思考学习参考 相似度计算 杰卡德&#xff08;Jaccard&#xff09;相似系数 Jaccard 系数是衡量两个…

AD9371 官方例程

文章目录 前言一、HDL方面1. ZYNQ 核根据ZCU106平台修改&#xff08;**参考UG1244 ZCU106 Evaluation Board**&#xff09;&#xff0c;尤其注意**DDR**的配置&#xff08;**参考美光 MTA4ATF51264HZ**&#xff09;2.dacfifo 深度 要修改 &#xff0c;地址位宽 小于等于16&…

【小黑嵌入式系统第二课】嵌入式系统的概述(二)——外围设备、处理器、ARM、操作系统

上一课&#xff1a; 【小黑嵌入式系统第一课】嵌入式系统的概述&#xff08;一&#xff09;——概念、特点、发展、应用 下一课&#xff1a; 【小黑嵌入式系统第三课】嵌入式系统硬件平台&#xff08;一&#xff09;——概述、总线、存储设备&#xff08;RAM&ROM&FLASH…

【周末闲谈】VR新视界,“眼”见未来

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 系列目录 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 系列目录前言虚拟现实(VR)技术虚拟现实技术的原理虚拟现实技术发…