B+树与索引

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

对于60%的程序员而言,Java的三层架构Controller、Service、Dao可以说是“越往后走天越黑”,特别是到了Dao层,提着灯笼也只能看到脚边一米开外的河边小石子,只闻对岸风啸马嘶却不知到底是人是鬼,只能借着MyBatis或JPA这些ORM框架隔着宽宽的河举行一场又一场的刺刀战,你砍我一刀,我刺你一剑。

诚然,很多人对MySQL数据库的印象就是一个模糊的大铁柜,闭上眼睛深吸一口气仿佛还能嗅到一股铁锈味。只知柜子里藏着很多张表,表里面存着很多行数据,再详细一点的呢?不知道。

本系列文章旨在帮大家以全新的视角重新认识MySQL的数据组织方式以及工作中最常用的SQL优化常识,带大伙乘一叶扁舟,到对岸亲眼瞧一瞧到底都是些什么神仙鬼怪。由于SQL优化是比较高深的内容,所以学习本文之前,请务必对SQL基础烂熟于心。

MySQL有太多太多细节,根本无法用四、五篇文章说透,但我仍希望这个系列的文章能成为非常好的入门教程,让从来没接触过SQL优化的同学也能快速建立较为系统的知识框架,方便日后学习其他专栏时进一步拓展。


柏青哥

大家小时候在游戏厅看过下面这种机器吗:

日本人管它叫柏青哥(パチンコ),玩法是:

从机器最上方的唯一入口投入一颗钢珠,由于重力的作用,钢珠会往下落。机器是直立的,面板上有很多突出的圆柱,它们的作用是随机改变钢珠的落点。最终,钢珠掉落在下方的某个槽中。

这里提柏青哥,是为后面的B+树及分析B+树搜索过程做铺垫。到时你会发现,沿着索引树搜索的过程和柏青哥小钢珠的下落过程是多么相似!

很多人都听过数据库索引,但是很少人会去思考下面几个问题:

  • 索引是什么
  • 为什么需要索引
  • 索引怎么起作用

在历史的长河中,索引的出现几乎是必然的。不信?那就跟我重走一遍历史吧。

请大家先忘了MySQL、Oracle等乱七八糟的玩意儿,就假设你是上世纪第一批程序员,此时连正儿八经的数据库都没有,需要由你来开天辟地。目前摆在你面前的最大难题是:如何较为高效地存取数据?

线性结构

最直观的想法就是存“格子”里,也就是将数据存在线性结构的容器中,比如数组或链表。

用线性结构存储数据短期看是没问题的,但是一家名为SUN的公司发现,随着公司业务增长,平台要经手的用户数据越来越多,特别是今年,他们收到了很多客户的信件投诉,说网页数据的加载越来越慢了!

SUN的工程师做了个实验,一个线性表如果存了42亿条数据,想要找到id=100的数据,游标只需爬99格即可返回,但如果id=10000000,就要爬将近1000w个格子才能返回。对于这42亿条数据,平均查询次数是21亿次!!

二叉查找树

作为改进,有人提出用树结构来存储数据。比如,如果要找id=6的数据,那么只要比较3次,小于爬格子次数(5次)

如果要找id=9的数据,只要比较4次,小于爬格子次数(8次)。

结合两次实验,SUN的工程师发现:

在一棵树中找到目标数据所需的比较次数 = 目标数据所在的层级

如果用一棵树来存储42亿条数据,即232=42亿,树的层级是32,最差的情况也只要查32次(需要是二叉平衡树),远远小于线性结构的平均21亿次,这是非常夸张的。

注意,在线性结构的案例中,不是我故意不用二分查找,而是不能使用二分查找。一般来说,对于线性结构的数据集合,如果要使用二分查找,那么整个数据都要事先在内存中,但显然没人会这样做!相比在内存中对线性结构进行二分查找,树在逻辑上天然支持二分查找。

然而,树结构也分好几种:

  • Binary Search Tree(二叉查找树)
  • AVL Tree(二叉平衡树)
  • B Tree(平衡树)
  • B+ Tree(大名鼎鼎的B+树,对B Tree的改进)
  • ...

大家可以访问Data Structure Visualizations这个网站动手玩一下,特别注意上面的4种树:

如果你听我的建议,打开上面的网站选择Binary Search Tree并按1,2,3,4...的顺序插入数据时,它其实变成了线性结构:

显然,这不是我们想要的结果,因为刚才已经讨论过,线性结构不适合存储大数据,等后期数据量大了以后要爬很多“格子”。

二叉平衡树

相比来说,AVL Tree更符合SUN工程师的需求:

二叉平衡树会在数据插入完毕后自动调整节点,好让“树的层级”不至于太深。(赶紧去动手玩一下)

按理来说,如果我们按二叉平衡树组织表数据的话,应该是非常完美的。你想啊,42亿数据中找一条记录最多只需比较32次,尤其是对于CPU来说,别说32次比较,哪怕32w次简单数据的比较都不会超过0.1秒。但是!问题在于这里所谓的“32w次简单数据的比较不会超过0.1秒”有个前提条件:数据必须全部在内存中。

而我们的表数据因为数据量很大,而且需要持久化,所以一般来说是存在磁盘中,等需要使用时再从磁盘载入内存,也就是涉及磁盘-内存的IO操作。

通常情况下,没有人会直接把500w行数据一次性加载到内存中进行二分查找,内存极有可能顶不住(同时访问多张表,全部加载)。所以,最终我们组织数据库的方式只能是:

  • 把数据存在磁盘中
  • 数据按树结构组织
  • 查询时分块读取数据并比较,持续进行磁盘IO读取节点,直到找到目标数据

每一个节点存储“一小块数据”,分多次IO读取每一块数据到内存判断,直到找到匹配的数据。

二叉平衡树与磁盘IO

数据存在磁盘中,没问题。

数据桉树结构组织,没问题。

查询时分块读取数据,有一点点问题。

磁盘IO是非常耗时的操作,耗时到什么程度呢?大家可能都听过各个语言的执行效率:

C > C++ > Java >> Python

但这些都是在内存层面谈论语言自身的执行效率,而实际上开发一个Web应用,无论用上述哪个语言,对于普通应用而言,都是可以满足用户需求的,真正的瓶颈是IO(网络IO和磁盘IO)。就好比F1赛车、奥迪A4和拖拉机一起跑在北京4环的路上,限制它们的不是引擎,而是堵车。

所以,二叉平衡树虽然查找42亿数据最多只需32次,但是32次磁盘IO还是不能接受的。

B树

基于上面的分析,如果考虑磁盘IO,那么原本优秀的二叉平衡树将显得不再那么优秀。错的不是二叉平衡树,而是我们没有那么大的内存,也不方便把数据都放内存(考虑数据丢失与持久化)。

但现在不是考虑谁对谁错的时候,要想优化当前数据库,关键是减少磁盘IO次数,而影响IO次数的关键因素就是树的层级(深度)!举个例子,如果目标数据在第二层,那么只要比较到第二层,就找到目标数据直接返回,不用再继续磁盘IO读取下一个节点。而如果数据在32层,那么就需要进行32次磁盘IO,比较到最后一层的节点。

那么,如何减少树的层级呢(让树变矮)?

请大家思考一下232中的“2”指的是什么?

其实就是“二叉平衡树”的“二”,而指数32代表树的层级。也就是说,如果以二叉平衡树的结构组织42亿行数据,那么树的深度是32。如果是“三叉平衡树”呢?

3?? = 232 

3的指数大概为21。也就是说,如果用“三叉树”组织数据,那么层级将会减少到21,也就意味着磁盘IO次数最多为21次。

所以,到这里我们已经有答案了:要想减少二叉平衡树的磁盘IO次数,需要增加它的“叉”,变成“N叉平衡树”,从而减少树的深度。

此时有位长者说了一句:你们心里没点B树吗。

听到这,SUN的工程师颇受启发:对哦,直接用B树就好了。

B树有个“阶”的概念,比如“三叉平衡”的B树其实叫“3阶B树”。

通过上面的图,我们会发现“N阶B树”每个节点可以存N-1个数据(二叉平衡树每个节点只存1个数据),且每个节点至多可以连接N个子节点。

这样组织的好处是,每次加载一个节点时都可以从磁盘带出更多条数据,从而减少磁盘IO的次数。比如原先比完id=3,接下来要和id=5比较,需要再从磁盘中把id=5的数据读出来。而现在当前节点已经有id=3,id=5的数据了,直接比较即可,无需做磁盘IO。

这是典型的“空间换时间”。

但B树最难的地方不是结构本身,而是如何实现这种结构,尤其是如何通过B树组织数据库的表数据?

举个例子,当我要找id=7的数据时,需要先找到根节点,和id=4的节点比较,由于7>4,所以选择右侧那一支,接着因为6<7<8,所以这个节点中三个addr选择中间的addr,顺着这个地址找到7的节点,然后取出数据。

需要注意的是,当一个节点被加载到内存后,这个节点内部的数据就可以通过二分查找得到啦,所以那么id=7去上图右边节点查找正确的addr是很快的。

上面只是演示了3阶B树,实际上1个节点可以存更多数据,做成N阶B树:

分析到这里,历史的话剧就告一个段落,让我们看看MySQL索引的真正实现方式吧。

B+树与索引

实际上MySQL索引采用的是B+树,而不是B树。

为什么不用B树呢?

在操作系统中有个叫“页”的概念,是用来存储数据的一种单位,大小为4k。MySQL中也有“页”的概念,但大小为16k,你可以理解为MySQL中的“页”就是上面B树的一个个节点。

那么问题来了:你知道日常开发中,表中的一行数据大概占多少字节吗?

让我们来计算一下:

在上面这张表中,按每列数据类型推算,一行数据大概 8+150+150+150+150+9+2+9+750+5+5+8+8=1404字节,就算1k好了,因为节点最大size是16k,所以每个节点最多只能存16行数据。

我们之前之所以从二叉平衡树转为B树,是因为B树的每个节点可以存更多数据。但上面的计算告诉我们,其实也就是比二叉平衡树多了15条数据而已。

但原则是对的,为了尽可能使树“变矮”从而减少磁盘IO,最好的做法是让一个节点尽可能地塞入更多的数据。

不过把整行数据塞到节点中,有点太浪费了,我们其实可以把每一行数据的主键存进去。即使用bigint类型做主键,一个主键也就8个字节。假设每个主键对应一个addr(指针),MySQL中一个指针为6个字节,那么节点内每个主键-地址这样形式的数据能存16*1024/14=1170个。

这其实就是B+树对B树的改造。

所谓的B+树,就是把原先B树中分散在各个节点的数据都“赶到”最底层的叶子节点,非叶子节点只存储主键-addr形式的数据:

最终,一棵3层的B+树,最底下的叶子节点总共能存2000w条数据。

有部分同学可能还是不明白B+树为什么比B树能存储更多数据,这里再举个最极端的例子,假设一行表数据8k,而一个节点容量是16k,如果是B Tree,那么一个节点只能存两行数据,最终每个节点只能“夹带”3个addr,只能指向3个子节点:

但如果是B+ Tree,只存主键:

那么最上面的节点可以存更多的主键,指向更多的下级节点,就有更多的“16k数据”。上面还只是分析单个节点的情况,如果放眼整棵索引树,最终叶子节点会多很多很多的“16k数据”。

从MySQL学习者的角度而言,我们只需要知道B+树2个很重要的特征:

  • 非叶子节点不存数据
  • 叶子节点数据用链表相连

所以更详细的版本是:

叶子节点是有序链表,可以帮助做范围查询。

最后,还有个问题,如果我不提估计很少有人会考虑:B+树如何查找数据。

为什么会有这个疑问呢?

之前说过,B树的节点存了完整的数据,假设数据总共3层,而你要找的数据在第2层的某个节点,当你找到后便可以直接返回整行数据。而B+树为了一个节点能存入更多的addr,节点内部只存了主键id,所以即使你在第二层找到匹配的id,还是不能直接返回,必须继续往下,直到在叶子节点读取完整数据。

讨论到这,我们来对比一下B树和B+树:

  • B树的节点都会存储整行数据,占用空间大存储addr少,而B+树的非叶子节点只存储主键,能容纳更多addr
  • 由于非叶子节点能容纳更多addr,那么同一个节点能指向更多下级节点,所以相同数据量时,B+树通常更加“矮”,IO更少
  • B树的查询效率是不稳定的,最好情况是根节点,最差情况是叶子节点,而B+树是稳定的,每次都要查询到叶子节点
  • B+树的叶子节点是有序列表,非常便于范围查询

对于第三点,很多人可能觉得B+树每次都是稳定地查询叶子节点,还不如B树(最好情况根节点就返回了)。其实上面分析过了,B+树每个节点能存储的数据是B树的1170/16≈73倍,意味着B+树每个节点可以连接的分支更多,相同数据量的情况下,B+树远远矮于B树。比如B树的查询IO次数是1~100,而B+树恒定为3,你觉得哪个效率高?

回头看看柏青哥,钢珠掉落的过程是不是很像沿着索引查找数据呢~

学到这里,相信Dao层的对岸到底是什么,大家心里已经有B+树了:

至此,我们回过头来给索引下个定义:

索引是一种数据结构,用于高效搜索目标数据,在MySQL中具体实现为B+树(InnoDB引擎)。

最后说一句,没有所谓的“B减树”,很多人可能看到B树的英文表示法是"B-Tree",误读为“B减树”。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

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

相关文章

【 某景点舆情分析:Python、Echarts、Flask、文本处理技术的应用】

某景点舆情分析&#xff1a;Python、Echarts、Flask、文本处理技术的应用 前言技术栈数据获取与准备景点数据统计分析评论数据处理与分析词频统计分词与文本处理情感分析 数据可视化Web应用搭建结语 前言 随着旅游行业的蓬勃发展&#xff0c;越来越多的人通过网络平台获取关于…

SQL 入门指南:从零开始学习 SQL

当今时代&#xff0c;数据已经成为了我们生活中不可或缺的一部分。无论是企业的经营决策&#xff0c;还是个人的日常消费习惯&#xff0c;都需要通过对数据的收集、分析和应用来实现更好的结果。 而关系型数据库系统&#xff0c;作为最常见的数据存储和管理方式&#xff0c;SQ…

HNU-数据库系统-实验1-数据定义/数据操纵

数据库系统 课程实验1数据定义/数据操纵 计科210X 甘晴void 202108010XXX 目录 文章目录 数据库系统 课程实验1<br>数据定义/数据操纵实验目的实验样例实验环境实验内容1.1 数据库定义1&#xff09;实验内容与要求2&#xff09;实验重难点3&#xff09;实验基础知识①模…

设计模式之结构型设计模式(二):工厂模式 抽象工厂模式 建造者模式

工厂模式 Factory 1、什么是工厂模式 工厂模式旨在提供一种统一的接口来创建对象&#xff0c;而将具体的对象实例化的过程延迟到子类或者具体实现中。有助于降低客户端代码与被创建对象之间的耦合度&#xff0c;提高代码的灵活性和可维护性。 定义了一个创建对象的接口&…

node.js mongoose简述

目录 官方文档 mongoose Schema Model Query document 关系 官方文档 Mongoose v8.0.3: Getting Started mongoose Mongoose 是一个 Node.js 环境下 MongoDB 的对象建模工具。它提供了一种在应用程序中与 MongoDB 数据库进行交互的方式&#xff0c;使得开发者能够使用…

数据可视化?这些平台能处

图表在各行各业都起到举重若轻的作用&#xff0c;无论是项目汇报、业绩分析&#xff0c;亦或是数据挖掘、统计分析&#xff0c;良好的可视化可以为我们的阐述起到画龙点睛的效果。在一篇文章中&#xff0c;如果只有密密麻麻的文字堆积&#xff0c;无论是谁恐怕都无法长期保持注…

mysql !=索引为什么失效

可以先看这个&#xff1a;Mysql查询条件为大于时&#xff0c;不走索引失效场景-CSDN博客 如下建立了联合索引&#xff0c; 注意查询phone是用不对劲了索引&#xff0c;key_len82 改为 !时&#xff0c; key key_len没内容&#xff0c;说明没有走索引&#xff0c;为什么没有走&…

DIY电脑装机机箱风扇安装方法

作为第一次自己diy一台电脑主机的我&#xff0c;在经历了众多的坑中今天来说一下如何安装机箱风扇的问题 一、风扇的数量 1、i3 xx50显卡 就用一个cpu散热风扇即可 2、i5 xx60 一个cpu散热风扇 一个风扇即可 3、i7 xx70 一个cpu散热 4个风扇即可 4、i9 xx80 就需要7个以…

Ubuntu安装蓝牙模块pybluez以及问题解决方案【完美解决】

文章目录 简介问题及解决办法总结 简介 近期因工程需要在Ubuntu中使用蓝牙远程一些设备。安装Bluetooth的Python第三方软件包pybluez时遇到很多问题&#xff0c;一番折腾后完美解决。此篇博客进行了梳理和总结&#xff0c;供大家参考。 问题及解决办法 pip install pybluez安…

数据迁移测试经验分享

以下为作者观点&#xff1a; 数据迁移&#xff0c;是在保证新旧系统业务连续性的前提下&#xff0c;将数据从旧数据库迁移到新数据库的过程&#xff0c;测试前通过迁移策略和方案了解新旧系统数据如何重构与关联&#xff0c;测试过程需确保数据迁移的正确性&#xff0c;主要体…

Lambda 表达式与C++并发编程

Lambda 表达式&#xff08;lambda expression&#xff09;是一个匿名函数&#xff0c;Lambda表达式基于数学中的λ演算得名&#xff0c;直接对应于其中的lambda抽象&#xff08;lambda abstraction&#xff09;&#xff0c;是一个匿名函数&#xff0c;即没有函数名的函数。 // …

Easy Excel生成复杂下Excel模板(下拉框)给用户下载

引言 文件的下载是一个非常常见的功能&#xff0c;也有一些非常好的框架可以使用&#xff0c;这里我们就介绍一种比较常见的场景&#xff0c;下载Excel模版&#xff0c;导入功能通常会配有一个模版下载的功能&#xff0c;根据下载的模版&#xff0c;填充数据然后再上传。 需求…

CAN 五: CAN编程实践

1、CAN基本驱动步骤 (1)CAN参数初始化 工作模式、波特率等函数&#xff1a;HAL_CAN_Init (2)使能CAN时钟和初始化相关引脚 GPIO模式设为复用功能模式函数&#xff1a;HAL_CAN_MspInit(CAN的初始化回调函数) (3)设置过滤器 过滤器的配置函数&#xff1a;HAL_CAN_ConfigFil…

客户端SDK测试是什么?如何测?

01、是什么 客户端SDK是为第三方开发者提供的软件开发工具包&#xff0c;包括SDK接口、开发文档和Demo示例等。SDK和应用之间是什么关系呢&#xff1f;以云信即时消息服务为例&#xff0c;如下图所示&#xff0c;应用客户端通过调用云信SDK接口&#xff0c;进行消息等数据查询…

用EnumSet代替位域

在Java中&#xff0c;可以使用EnumSet来代替位域&#xff0c;以提高代码的可读性和类型安全性。下面是一个简单的例子&#xff0c;演示如何使用EnumSet来管理一组枚举值&#xff1a; import java.util.EnumSet;// 定义一个枚举类型表示权限 enum Permission {READ, WRITE, EXE…

【Java】使用递归的方法获取层级关系数据demo

使用递归来完善各种业务数据的层级关系的获取 引言&#xff1a;在Java开发中&#xff0c;我们通常会遇到层层递进的关系型数据的获取问题&#xff0c;有时是树状解构&#xff0c;或金字塔结构&#xff0c;怎么描述都行&#xff0c;错综复杂的关系在程序中还是可以理清的。 这…

【MySQL】SQL通用语法 、介绍SQL分类

SQL通用语法 1.SQL语句可以单行或多行书写&#xff0c;以分号结尾 2.MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写。 3.注释&#xff1a; 单行注释&#xff1a; -- 或 # 多行注释: /* */ SQL分类 SQL分类主要分为4类 分别是 DDL DML DQL DCL

向华为学习:基于BLM模型的战略规划研讨会实操的详细说明,含研讨表单(一)

前面&#xff0c;华研荟用了三篇文章介绍华为战略规划的时候使用的其中一个工具&#xff1a;五看三定。一句话来说&#xff0c;五看三定是通过“五看”来知己知彼&#xff0c;然后设计业务&#xff0c;在选定的业务领域&#xff08;方向&#xff09;确定战略控制点&#xff0c;…

libp2p 快速开始

文章目录 第一部分&#xff1a;libp2p 快速入门一、什么是libp2plibp2p 发展历程libp2p的特性p2p 网络和我们熟悉的 client/server 网络的区别&#xff1a; 二、Libp2p的实现目标三、Libp2p的用途四、运行 Libp2p 协议流程libp2p 分为三层libp2p 还有一个局域网节点发现协议 mD…

新能源汽车的“一池春水”,或许不再由价格战掀起波澜?

2005年12月15日&#xff0c;丰田普锐斯混合动力车进入中国&#xff0c;拉开了国内新能源汽车发展的序幕。18年后的今天&#xff0c;国产及进口的新能源汽车已经渗透我国超三分之一的乘用车市场&#xff0c;与油车二分天下的愿景渐趋实现。 今年11月&#xff0c;随着购车需求进…