MySQL优化(1):B+树与索引

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

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

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

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

本文旨在帮大家以全新的视角重新认识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亿次,这是非常夸张的。

有人可能会钻牛角尖,说我没有对线性结构中42亿条数据采用二分查找法...真是冤枉,这完全是两个概念。

眼前就是一棵存了42亿数据的树 和 一条存了42亿数据的长条格子,“焊死”在那了,而且指针都是从第一个位置开始的,每移动一次就比较一次,直到找到匹配的数据。所以,我们是无法直接把42亿数据读取到内存中进行二分查找的。

只不过二叉树结构刚好天然就可以进行二分查找,所以效率非常高。

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

  • 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是非常耗时的操作,耗时到什么程度呢?大家可能都听过各个语言的执行效率:

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树”。

通过上面的图,我们会发现B树每个节点可以存多行数据(二叉平衡树每个节点只存一行数据),且每个节点可以连接至少2个节点,但最多连接数不超过M(M是当前B树的阶数)。

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

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

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

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

至于如何根据id范围确定addr,以及节点内部addr和表数据是如何组织的,可以不用深究。但有一点要知道:节点内的数据比较是很快的,因为它本身是在内存中,且据说也是二分查找。

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

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

B+树与索引

上面分析了B树如何实现索引,但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,如果是B Tree,由于最上面的节点存了整行数据,所以只能存两行,最终有3个addr,只能指向3个其他节点:

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

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

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

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

所以更详细的版本是:

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

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

比如,B树的每个节点是有整行数据的,比如我想找id=4的数据,因为id=4的节点有完整的数据,可以直接返回:

但是,B+树怎么办?非叶子节点只有主键,如果我要找id=7的数据,到了007节点是不能直接返回的:

B+树是怎么解决这个问题的呢?去上面提到的那个网站动手试验一下即可:

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

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

光看上面的图,大家可能觉得B树反而更“矮”,这是因为我们给B树和B+树都选择了3阶,实际上B+树可以存更多,让树变得“更矮”。

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

回头看看柏青哥,像不像一棵B+树呢~

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

数据库里一张张表,其实都可以看做一颗颗B+树。索引即数据,数据即索引(对于主键索引而言)。

对于MyISAM和InnoDB可以简单总结如下:

  • MyISAM:非聚簇索引
  • InnoDB:
    • 聚簇索引:主键索引,叶子节点是表数据
    • 非聚簇索引:辅助索引(唯一索引、普通索引),叶子节点是主键,必要时需要根据主键回表查询
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

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

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

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

相关文章

ER 图是什么

文章目录 前言什么是 ER图ER 图实例简化的 ER 图总结 前言 产品经理在梳理产业业务逻辑的过程中&#xff0c;非常重要的一项工作就是梳理各个业务对象之间的关系。如果涉及对象很对的时候&#xff0c;没有工具支持的话很难处理清楚。今天我们就来介绍一个梳理业务对象关系的工…

数据结构与集合源码

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷F

1、常见的加密算法可以分为对称加密算法和非对称加密算法&#xff0c;以下关于它们的描述正确的是 A、AES是一种常见的非对称加密算法 B、凯撒密码是一种非对称加密 C、非对称加密算法的解密使用的秘钥与加密不同 D、对称加密算法无法被暴力破解 答案&#xff1a;C 2、12根…

ESP32 MicroPython UART及小车类构造函数实验⑥

ESP32 MicroPython UART及小车类构造函数实验⑥ 1、实验目的2、实验内容3、参考代码4、实验结果 1、实验目的 控制小车动起来 2、实验内容 控制小车的前进、后退、左转、右转。读取小车 使用到的串口构造函数&#xff1a; uartmachine.UART(id,baudrate,rx,tx)uart:返回的构…

C++多态原理揭秘

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

二进制分析工具-radare2使用教程

二进制分析工具-radare2使用教程 按照如下执行命令 按照如下执行命令 r2 -A 二进制文件

我为什么开始写技术博客

今天没有技术文章&#xff0c;只是想聊聊认真做CSDN和公众号以来的一些感想。 1.为什么开启技术分享 我不算是一个聪明的人&#xff0c;没有过目不忘的本事&#xff0c;所以从工作开始就养成了做笔记的习惯&#xff1b; 最开始15、16年做模型开发&#xff0c;那时候环境其实就…

*ST富吉-688272 三季报分析(20231117)

*ST富吉-688272 基本情况 公司名称&#xff1a;北京富吉瑞光电科技股份有限公司 A股简称&#xff1a;*ST富吉 成立日期&#xff1a;2011-01-20 上市日期&#xff1a;2021-10-18 所属行业&#xff1a;计算机、通信和其他电子设备制造业 周期性&#xff1a;1 主营业务&#xff1a…

23111702[含文档+PPT+源码等]计算机毕业设计javaweb高校宿舍管理系统寝室管理

文章目录 **软件开发环境及开发工具&#xff1a;****项目功能介绍&#xff1a;****论文截图&#xff1a;****实现&#xff1a;****代码片段&#xff1a;** 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 软件开发环境及开发工具&#xff…

Excel 文件比较工具 xlCompare 11.01 Crack

比较两个 Excel 文件之间的差异 xlCompare. xlCompare.com 是性能最佳的 Excel diff 工具&#xff0c;用于比较两个 Excel 文件或工作表并在线突出显示差异。xlCompare 包括免费的在线 Excel 和 CSV 文件比较服务以及用于比较和合并 Excel 文件的强大桌面工具。如果您想在线了…

STM32与ZigBee无线通信技术在工业自动化中的应用

工业自动化是指利用电子技术、计算机技术和通信技术等手段&#xff0c;对工厂、设备和生产过程进行自动化控制和管理的过程。在工业自动化中&#xff0c;可靠的无线通信技术对于实时数据的传输和设备的协同控制至关重要。本文将介绍STM32微控制器与ZigBee无线通信技术在工业自动…

MySQL 运算符二

逻辑运算符 逻辑运算符用来判断表达式的真假。如果表达式是真&#xff0c;结果返回 1。如果表达式是假&#xff0c;结果返回 0。 运算符号作用NOT 或 !逻辑非AND逻辑与OR逻辑或XOR逻辑异或 1、与 mysql> select 2 and 0; --------- | 2 and 0 | --------- | 0 | -…

python django 小程序商城源码

开发环境&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html vue.js bootstrap 微信小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索商品(可根据…

SQL 的 AND、OR 和 NOT 运算符:条件筛选的高级用法

AND 运算符 SQL的AND运算符用于根据多个条件筛选记录&#xff0c;确保所有条件都为TRUE才返回记录。下面是AND运算符的基本语法&#xff1a; SELECT column1, column2, ... FROM table_name WHERE condition1 AND condition2 AND condition3 ...;column1, column2,等是您要选…

“具有分布式能源资源的多个智能家庭的能源管理的联邦强化学习”文章学习四——基于联邦深度学习的多智能家居能源管理

一、用于家庭能源管理的FRL算法 在本节中&#xff0c;我们将阐述提出的FRL算法&#xff08;算法1&#xff09;&#xff0c;该算法以分布式方式调度多个智能家庭的能量消耗。在提出的FRL框架中&#xff0c;LHEMS和GS相互迭代并有效训练LHEMS的模型。我们考虑了由LHEMS控制的空调…

Python | 机器学习之SVM支持向量机

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《人工智能奇遇记》&#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 目录结构 1. 机器学习之SVM支持向量机概念 1.1 机器学习 1.2 SVM支持向量机 2. SVM支持向量机…

这就是不创业的最大的原因,机器视觉兄弟们创业要谨慎,为什么99.99%机器视觉公司老板是销售人员?

机器视觉公司&#xff0c;99%公司实行销售优先原则&#xff0c;企业老板99%从销售人员中产生。所以机器视觉兄弟们创业要谨慎。 企业的本质就是通过销售获得利润的组织&#xff0c;销售是立足之本&#xff0c;没有销售&#xff0c;创业就是耍流氓。因此&#xff0c;企业的一切…

基于SpringBoot的SSMP整合案例(消息一致性处理与表现层开发)

消息一致性处理 在后端执行完相应的操作后&#xff0c;我们需要将执行操作后的结果与数据返回前端&#xff0c;前端 调用我们传回去的数据&#xff0c;前端是如何知道我们传回去的数据名称的&#xff1f; 答&#xff1a;前后端遵循了同一个"协议"。这个协议就是定义…

贝茄莱BR AS实时数据采集功能

实时数据采集功能在PLC系统调试过程中&#xff0c;有助于调试人员对变量变化进行监测&#xff0c;通过波形对比&#xff0c;反应不同变量间的相互作用。该测试目的在于验证贝加莱系统组态软件的实时数据采集功能。 贝加莱系统组态软件提供Trace功能&#xff0c;连接PLC&#x…

国产高云FPGA:OV5640图像视频采集系统,提供Gowin工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐国产高云FPGA相关方案推荐国产高云FPGA基础教程 3、设计思路框架视频源选择OV5640摄像头配置及采集动态彩条Video Frame Buffer 图像缓存DDR3 Memory Interface 4、Gowin工程详解5、上板调试验证并演示准备工作静态演示 6、福利&#xff1…