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

作者简介:大家好,我是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.hqwc.cn/news/189732.html

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

相关文章

智能驾驶汽车虚拟仿真视频数据理解(一)

赛题官网 datawhale 赛题介绍 跑通demo paddle 跑通demo torch 提交的障碍物取最主要的那个&#xff1f;不考虑多物体提交。障碍物&#xff0c;尽可能选择状态发生变化的物体。如果没有明显变化的&#xff0c;则考虑周边的物体。车的状态最后趋于减速、停止&#xff0c;时序…

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

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

python django 小程序商城源码

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

Elasticsearch:通过摄取管道加上嵌套向量对大型文档进行分块轻松地实现段落搜索

作者&#xff1a;VECTOR SEARCH 向量搜索是一种基于含义而不是精确或不精确的 token 匹配技术来搜索数据的强大方法。 然而&#xff0c;强大的向量搜索的文本嵌入模型只能按几个句子的顺序处理短文本段落&#xff0c;而不是可以处理任意大量文本的基于 BM25 的技术。 现在&…

《网络协议》08. 概念补充

title: 《网络协议》08. 概念补充 date: 2022-10-06 18:33:04 updated: 2023-11-17 10:35:52 categories: 学习记录&#xff1a;网络协议 excerpt: 代理、VPN、CDN、网络爬虫、无线网络、缓存、Cookie & Session、RESTful。 comments: false tags: top_image: /images/back…

机器视觉选型-什么时候用远心镜头

物体厚 当被检测物体厚度较大&#xff0c;需要检测不止一个平面时&#xff0c;典型应用如食品盒&#xff0c;饮料瓶等。 物体位置变化 当被测物体的摆放位置不确定&#xff0c;可能跟镜头成一定角度时。 物体上下跳动 当被测物体在被检测过程中上下跳动&#xff0c;如生产线上下…

一起学docker系列之四docker的常用命令--系统操作docker命令及镜像命令

目录 前言1 操作 Docker 的命令1.1 启动 Docker1.2 停止 Docker1.3 重启 Docker1.4 查看 Docker 状态1.5 查看 Docker 所有命令的信息1.6 查看某个命令的帮助信息 2 操作镜像的命令2.1 查看所有镜像2.2 搜索某个镜像2.3 下载某个镜像2.4 查看镜像所占空间2.5 删除镜像2.6 强制删…

ESP32 MicroPython LCD显示实验⑤

ESP32 MicroPython LCD显示实验⑤ 1、实验目的2、实验平台3、实验内容4、参考代码5、实验结果 1、实验目的 LCD显示屏显示中英文字符、显示图片 2、实验平台 智能小车(配备显示屏) 3、实验内容 小车配有2.0寸的TFT彩屏&#xff0c;内置有中文GBK字库&#xff0c;可以显示中…

C++初级项目-webserver(1)

1.引言 Web服务器是一个基于Linux的简单的服务器程序&#xff0c;其主要功能是接收HTTP请求并发送HTTP响应&#xff0c;从而使客户端能够访问网站上的内容。本项目旨在使用C语言&#xff0c;基于epoll模型实现一个简单的Web服务器。选择epoll模型是为了高效地处理大量并发连接…

数据库mysql详细教学

1024 byte 构成 1 kb 1024 KB > 1MB 1024 MB > 1GB 1024 GB > 1TB 1024 TB > 1PB 内存的数据&#xff0c;断电后会丢失。外存的数据&#xff0c;断电后数据还在~ “持久化” 这样的次&#xff0c;意思就是把数据写到硬盘上。 mysql的第一组基本操作&#xff1a;数…

我为什么开始写技术博客

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

MySQL优化(2):索引与优化原理(上)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 上一篇&#xff0c;我们…