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.mzph.cn/news/146516.shtml

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

相关文章

springboot-RedisTemplate

pom.xml: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…

嵌入式QTGit面试题

自己在秋招过程中遇到的QT和嵌入式和Git相关的面试题&#xff0c;因为比较少就一起放了 QT connect第5个参数是什么&#xff1f; Qt::AutoConnection&#xff1a; 默认值&#xff0c;使用这个值则连接类型会在信号发送时决定。 如果接收者和发送者在同一个线程&#xff0c;则…

【机器学习】决策树算法理论:算法原理、信息熵、信息增益、预剪枝、后剪枝、算法选择

1. 决策树概念 通过不断的划分条件来进行分类&#xff0c;决策树最关键的是找出那些对结果影响最大的条件&#xff0c;放到前面。 我举个列子来帮助大家理解&#xff0c;我现在给我女儿介绍了一个相亲对象&#xff0c;她根据下面这张决策树图来进行选择。比如年龄是女儿择偶更…

【如何学习Python自动化测试】—— 自动化测试环境搭建

1、 自动化测试环境搭建 1.1 为什么选择 Python 什么是python&#xff0c;引用python官方的说法就是“一种解释型的、面向对象、带有励志语义的高级程序设计语言”&#xff0c;对于很多测试人员来说&#xff0c;这段话包含了很多术语&#xff0c;而测试人员大多是希望利用编程…

和鲸科技创始人范向伟受邀出席“凌云出海,来中东吧”2023华为云上海路演活动

11月9日&#xff0c;华为云“凌云出海&#xff0c;来中东吧”系列路演活动第二场在上海正式开启。聚焦“创业全球化”&#xff0c;本次活动由华为云携手阿布扎比投资办公室&#xff08;ADIO&#xff09;举办&#xff0c;旨在与渴望出海发展的优秀创业者们共探出海中东新商机。 …

Vue3-watchEffect函数

Vue3-watchEffect函数 功能&#xff1a;watchEffect 函数在一开始时就会执行一次&#xff0c;而当中的回调函数的属性发生变化&#xff0c;那么watchEffect 就会再执行一次&#xff0c;主要作用还是在于监视回调函数每次的变化。 // App.vue <template><h2>计数…

一个美观且功能丰富的 .NET 控制台应用程序开源库

推荐一个美观且功能丰富的 .NET 控制台应用程序开源库&#xff0c;从此告别黑漆漆的界面。 01 项目简介 Spectre.Console 是一个开源的 .NET 库&#xff0c;用于创建美观、功能丰富的控制台&#xff08;命令行&#xff09;应用程序。它提供了一组易于使用的 API&#xff0c;…

阿里云崩了,总结我们从云上搬到线下经历了什么

我们做钢铁行业云的时候&#xff0c;也曾购买过某讯的云服务器。当时某讯做活动&#xff0c;头3年比较便宜&#xff0c;大概买了40台左右云服务器。 但是&#xff0c;3年期间使用云服务器的经历&#xff0c;体验并不好&#xff1a;1.我们云服务器的密码都是随机生成的&#xff…

多视图聚类的论文阅读

当聚类的方式使用的是某一类预定义好的相似性度量时&#xff0c; 会出现如下情况&#xff1a; 数据聚类方面取得了成功&#xff0c;但它们通常依赖于预定义的相似性度量&#xff0c;而这些度量受原始方法的影响:当输入维数相对较高时&#xff0c;往往是无效的。 1. Deep Mult…

解决Requests中使用httpbin服务器问题:自定义URL的实现与验证

问题背景 在使用Python的Requests模块进行单元测试时&#xff0c;可能会遇到无法使用本地运行的httpbin服务器进行测试的问题。这是因为测试脚本允许通过环境变量HTTPBIN_URL指定用于测试的本地httpbin实例&#xff0c;但在某些测试用例中&#xff0c;URL是硬编码为httpbin.or…

Linux 系统目录结构

Linux 系统目录结构 登录系统后&#xff0c;在当前命令窗口下输入命令&#xff1a; ls / 你会看到如下图所示: 以下是对这些目录的解释&#xff1a; /bin&#xff1a; bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。 /boot&#xff1a; 这里存放…

PbootCMS 应用快速迁移至浪潮信息 KeyarchOS 云服务器

本文我们以 PbootCMS 应用为例&#xff0c;详细介绍如何使用 X2Keyarch 迁移工具将其从 CentOS 系统迁移到浪潮信息 KeyarchOS 系统。 背景介绍 众所周知&#xff0c;CentOS 是最流行的红帽克隆版&#xff0c;因为免费&#xff0c;所以它的安装量甚至比红帽本身要大得多。本来…

在Rust编程中使用泛型

1.摘要 Rust中的泛型可以让我们为像函数签名或结构体这样的项创建定义, 这样它们就可以用于多种不同的具体数据类型。下面的内容将涉及泛型定义函数、结构体、枚举和方法, 还将讨论泛型如何影响代码性能。 2.在函数定义中使用泛型 当使用泛型定义函数时&#xff0c;本来在函…

Aerial for Mac: 沉浸在高清鸟瞰的世界,让你的屏幕焕发新生

你是否已经厌倦了那些平淡无奇的屏保程序&#xff1f;是否希望你的Mac屏幕能更生动、更有趣&#xff1f;如果你对此抱有强烈的期待&#xff0c;那么Aerial for Mac绝对会是你期待已久的解决方案。 Aerial for Mac是一款独具特色的高清屏保程序&#xff0c;它以鸟瞰的视角带你领…

【Android】带下划线的TextView

序言 我们有时候需要一个类似这样的显示&#xff0c;上面是文字&#xff0c;下面是一条线 这样的显示效果是TextView实现不了的&#xff0c;需要我们自己进行修改一下。 实现 创建一个UnderlineTextView&#xff0c;继承系统的TextView class UnderlineTextView(mContext…

安装 eslint 配置指南 及 遇到的一些问题记录

前端eslint配置指南 背景 当前前端项目风格混乱&#xff0c;每个人有自己的开发习惯&#xff0c;有自己的格式化习惯&#xff0c;不便于项目的风格统一&#xff0c;不利于代码维护有的项目eslint没有用起来&#xff0c;没有起到规范代码的作用&#xff0c;导致出现一些基础代码…

MySQL-事务

什么是事务 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 事务的特性 (ACID) 原子性(Atomicity)&#xff1a;事务是不…

C_11微机原理

一、单项选择题&#xff08;本大题共 15 小题&#xff0c;每小题 3分&#xff0c;共45分。在每小题给出的四个备选项中&#xff0c;选出一个正确的答案。&#xff09; .EXE 文件产生在&#xff08;&#xff09;之后。 A.汇编 B. 编辑 C.用软件转换 D.连接 2,十进制-61的8位二进…

MybatisPlus学习

一.快速入门 1.相关数据库创建 CREATE TABLE USER(id BIGINT(20) NOT NULL COMMENT 主键ID,NAME VARCHAR(30) NULL DEFAULT NULL COMMENT 姓名,age INT(11) NULL DEFAULT NULL COMMENT 年龄,email VARCHAR(50) NULL DEFAULT NULL COMMENT 邮箱,PRIMARY KEY (id));​​INSERT I…

《洛谷深入浅出基础篇》——P3405 citis and state ——哈希表

上链接&#xff1a;P3405 [USACO16DEC] Cities and States S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3405 上题干&#xff1a; 题目描述 Farmer John 有若干头奶牛。为了训练奶牛们的智力&#xff0c;Farmer John 在谷仓的墙上放了一…