MySQL相关(三)- 索引数据模型推演及 B+Tree 的详细介绍

前言

前面已经写了有两篇章长度的文章,第三篇我一直在寻思着要写什么(其实并无),按照脑图来的话,这篇文章咱们该来说讲关于索引的知识了,这但是 MySQL 性能优化很关键的知识点,千万千万不要错过,不过我这里会相对比较深刻地探究,相信你们读完以后多少会有点收获。php

先送上两张飞机票🛬还没读过前面文章的伙伴能够先前往阅读,由浅入深:html

MySQL相关(一)- 一条查询语句是如何执行的mysql

MySQL相关(二)- 一条更新语句是如何执行的程序员

因为索引的知识点比较多,官网的内容也不少,若是你们想详细了解能够到官网,想先通读了解的话能够先看看我对索引的总结,这一章节分为三部分来说:面试

  1. innodb 逻辑存储结构须要了解,做为番外篇

MySQL相关(番外篇)- innodb 逻辑存储结构算法

  1. 索引的数据结构也做为另外的篇章,经过对查询算法的数据模型进行演算分析

MySQL相关(三)- 索引数据模型推演及 B+Tree 的详细介绍sql

  1. 对索引的使用及优化规则也会做为单独的篇章

MySQL相关(四)- 性能优化关键点索引数据库

前面提到的脑图以下,想要完整高清图片能够到微信个人公众号下【6曦轩】下回复 MySQL 脑图获取: 数组

在这里插入图片描述

正文

MySQL索引数据模型推演

二分查找

双十一过去以后,你女友跟你玩了一个猜数字的游戏。(假设程序员 new 了一个会购物的女友出来) 猜猜我昨天买了多少钱,给你五次机会。 10000?低了。30000?高了。 接下来你会猜多少? 20000。为何你不猜 11000,也不猜 29000 呢?性能优化

其实这个就是二分查找的一种思想,也叫折半查找,每一次,咱们都把候选数据缩小了一半。若是数据已经排过序的话,这种方式效率比较高。

因此第一个,咱们能够考虑用有序数组做为索引的数据结构。

有序数组的等值查询和比较查询效率很是高,可是更新数据的时候会出现一个问题,可能要挪动大量的数据(改变 index),因此只适合存储静态的数据。

为了支持频繁的修改,好比插入数据,咱们须要采用链表。链表的话,若是是单链表,它的查找效率仍是不够高。

那么,有没有可使用二分查找的链表呢?

为了解决这个问题,BST(Binary Search Tree)也就是咱们所说的二叉查找树诞生了。

二叉查找树

二叉查找树的特色是什么? 左子树全部的节点都小于父节点,右子树全部的节点都大于父节点。投影到平面之后,就是一个有序的线性表。

在这里插入图片描述

二叉查找树既可以实现快速查找,又可以实现快速插入。

  • 可是二叉查找树有一个问题:

它的查找耗时是和这棵树的深度相关的,在最坏的状况下时间复杂度会退化成 O(n)。

  • 什么状况是最坏的状况呢?

咱们打开这样一个网站来看一下,这里面有各类各样的数据结构的动态演示,包括 BST 二叉查找树: www.cs.usfca.edu/~galles/vis… 仍是刚才的这一批数字,若是咱们插入的数据恰好是有序的,二、六、十一、1三、1七、 22。 这个时候咱们的二叉查找树变成了什么样了呢?

在这里插入图片描述
它会变成链表(这种树也叫作“斜树”),这种状况下不能达到加快检索速度的目的,和顺序查找效率是没有区别的。

  • 形成它倾斜的缘由是什么呢?

由于左右子树深度差太大,这棵树的左子树根本没有节点——也就是它不够平衡。

  • 因此,咱们有没有左右子树深度相差不是那么大,更加平衡的树呢?

这个就是平衡二叉树,叫作 Balanced binary search trees,或者 AVL 树(AVL 是发明这个数据结构的人的名字)。

平衡二叉树

AVL Trees (Balanced binary search trees) 平衡二叉树的定义:

左右子树深度差绝对值不能超过 1。

  • 是什么意思呢?

好比左子树的深度是 2,右子树的深度只能是 1 或者 3。 这个时候咱们再按顺序插入 一、二、三、四、五、6,必定是这样,不会变成一棵“斜树”。

  • 那它的平衡是怎么作到的呢?怎么保证左右子树的深度差不能超过 1 呢?

www.cs.usfca.edu/~galles/vis… 插入 一、二、3。 咱们注意看:当咱们插入了 一、2 以后,若是按照二叉查找树的定义,3 确定是要在 2 的右边的,这个时候根节点 1 的右节点深度会变成 2,可是左节点的深度是 0,由于它没有子节点,因此就会违反平衡二叉树的定义。

右-右型
左旋平衡
那应该怎么办呢?由于它是右节点下面接一个右节点,右-右型,因此这个时候咱们要把 2 提上去,这个操做叫作左旋。
左-左型
右旋
一样的,若是咱们插入 七、六、5,这个时候会变成左左型,就会发生右旋操做,把 6 提上去。 因此为了保持平衡,AVL 树在插入和更新数据的时候执行了一系列的计算和调整的操做。

  • 平衡的问题咱们解决了,那么平衡二叉树做为索引怎么查询数据?

  • 在平衡二叉树中,一个节点,它的大小是一个固定的单位,做为索引应该存储什么内容?

它应该存储三块的内容:

  1. 索引的键值。好比咱们在 id 上面建立了一个索引,我在用 where id =1 的条件查询的时候就会找到索引里面的 id 的这个键值。

  2. 数据的磁盘地址,由于索引的做用就是去查找数据的存放的地址。

  3. 由于是二叉树,它必须还要有左子节点和右子节点的引用,这样咱们才能找到下一个节点。好比大于 26 的时候,走右边,到下一个树的节点,继续判断。

若是是这样存储数据的话,咱们来看一下会有什么问题。 在分析用 AVL 树存储索引数据以前,咱们先来学习一下 InnoDB 的逻辑存储结构。 innodb 的逻辑存储结构

AVL 树用于存储索引数据

首先,索引的数据,是放在硬盘上的。查看数据和索引的大小:

SELECT
	CONCAT(
		ROUND(SUM(DATA_LENGTH / 1024 / 1024) , 2) ,
		'MB'
	) AS data_len ,
	CONCAT(
		ROUND(SUM(INDEX_LENGTH / 1024 / 1024) , 2) ,
		'MB'
	) AS index_len
FROM
	information_schema. TABLES
WHERE
	table_schema = 'gupao'
AND table_name = 'user_innodb';
复制代码

当咱们用树的结构来存储索引的时候,访问一个节点就要跟磁盘之间发生一次 IO。

InnoDB 操做磁盘的最小的单位是一页(或者叫一个磁盘块),大小是 16K(16384 字节)。

那么,一个树的节点就是 16K 的大小。

若是咱们一个节点只存一个键值+数据+引用,例如整形的字段,可能只用了十几个或者几十个字节,它远远达不到 16K 的容量,因此访问一个树节点,进行一次 IO 的时候,浪费了大量的空间。

因此若是每一个节点存储的数据太少,从索引中找到咱们须要的数据,就要访问更多的节点,意味着跟磁盘交互次数就会过多。

若是是机械硬盘时代,每次从磁盘读取数据须要 10ms 左右的寻址时间,交互次数越多,消耗的时间就越多。

在这里插入图片描述
好比上面这张图,咱们一张表里面有 6 条数据,当咱们查询 id=37 的时候,要查询两个子节点,就须要跟磁盘交互 3 次,若是咱们有几百万的数据呢?这个时间更加难以估计。

  • 因此咱们的解决方案是什么呢?
  1. 让每一个节点存储更多的数据。
  2. 节点上的关键字的数量越多,咱们的指针数也越多,也就是意味着能够有更多的分叉(咱们把它叫作“路数”)。

由于分叉数越多,树的深度就会减小(根节点是 0)。

这样,咱们的树是否是从原来的高瘦高瘦的样子,变成了矮胖矮胖的样子?

这个时候,咱们的树就再也不是二叉了,而是多叉,或者叫作多路。

多路平衡查找树(B Tree)(分裂、合并)

Balanced Tree 这个就是咱们的多路平衡查找树,叫作 B Tree(B 表明平衡)。 跟 AVL 树同样,B 树在枝节点和叶子节点存储键值、数据地址、节点引用。 它有一个特色:分叉数(路数)永远比关键字数多 1。好比咱们画的这棵树,每一个节 点存储两个关键字,那么就会有三个指针指向三个子节点。

在这里插入图片描述
B Tree 的查找规则是什么样的呢? 好比咱们要在这张表里面查找 15。 由于 15 小于 17,走左边。 由于 15 大于 12,走右边。 在磁盘块 7 里面就找到了 15,只用了 3 次 IO。 这个是否是比 AVL 树效率更高呢? 那 B Tree 又是怎么实现一个节点存储多个关键字,还保持平衡的呢?跟 AVL 树有什 么区别?

www.cs.usfca.edu/~galles/vis…

好比 Max Degree(路数)是 3 的时候,咱们插入数据 一、二、3,在插入 3 的时候, 原本应该在第一个磁盘块,可是若是一个节点有三个关键字的时候,意味着有 4 个指针, 子节点会变成 4 路,因此这个时候必须进行分裂。把中间的数据 2 提上去,把 1 和 3 变 成 2 的子节点。

若是删除节点,会有相反的合并的操做。 注意这里是分裂和合并,跟 AVL 树的左旋和右旋是不同的。 咱们继续插入 4 和 5,B Tree 又会出现分裂和合并的操做。

在这里插入图片描述
从这个里面咱们也能看到,在更新索引的时候会有大量的索引的结构的调整,因此解释了为何咱们不要在频繁更新的列上建索引,或者为何不要更新主键。 节点的分裂和合并,其实就是 InnoDB 页的分裂和合并。

B+树(增强版多路平衡查找树)

B Tree 的效率已经很高了,为何 MySQL 还要对 B Tree 进行改良,最终使用了 B+Tree 呢?整体上来讲,这个 B 树的改良版本解决的问题比 B Tree 更全面。 咱们来看一下 InnoDB 里面的 B+树的存储结构:

在这里插入图片描述

MySQL 中的 B+Tree 有几个特色:

  1. 它的关键字的数量是跟路数相等的;
  2. B+Tree 的根节点和枝节点中都不会存储数据,只有叶子节点才存储数据。搜索 到关键字不会直接返回,会到最后一层的叶子节点。好比咱们搜索 id=28,虽然在第一 层直接命中了,可是所有的数据在叶子节点上面,因此我还要继续往下搜索,一直到叶 子节点。 举个例子:假设一条记录是 1K,一个叶子节点(一页)能够存储 16 条记录。非叶 子节点能够存储多少个指针? 假设索引字段是 bigint 类型,长度为 8 字节。指针大小在 InnoDB 源码中设置为 6 字节,这样一共 14 字节。非叶子节点(一页)能够存储 16384 / 14 = 1170 个这样的 单元(键值+指针),表明有 1170 个指针。 树深度为 2 的时候, 有 1170^2 个叶子节点 ,能够存储的数据为 1170 * 1170 * 16 = 21902400。 在查找数据时一次页的查找表明一次 IO,也就是说,一张 2000 万左右的表,查询数据最多须要访问 3 次磁盘。 因此在 InnoDB 中 B+ 树深度通常为 1-3 层,它就能知足千万级的数据存储。
  3. B+Tree 的每一个叶子节点增长了一个指向相邻叶子节点的指针,它的最后一个数 据会指向下一个叶子节点的第一个数据,造成了一个有序链表的结构。
  4. 它是根据左闭右开的区间 [ )来检索数据。

咱们来看一下 B+Tree 的数据搜寻过程:

1)好比咱们要查找 28,在根节点就找到了键值,可是由于它不是页子节点,因此会继续往下搜寻,28 是[28,66)的左闭右开的区间的临界值,因此会走中间的子节点,而后继续搜索,它又是[28,34)的左闭右开的区间的临界值,因此会走左边的子节点,最后在叶子节点上找到了须要的数据。

2)第二个,若是是范围查询,好比要查询从 22 到 60 的数据,当找到 22 以后,只须要顺着节点和指针顺序遍历就能够一次性访问到全部的数据节点,这样就极大地提升了区间查询效率(不须要返回上层父节点重复遍历查找)。

总结一下,InnoDB 中的 B+Tree 的特色:

  1. 它是 B Tree 的变种,B Tree 能解决的问题,它都能解决。B Tree 解决的两大问题是什么?(每一个节点存储更多关键字;路数更多)
  2. 扫库、扫表能力更强(若是咱们要对表进行全表扫描,只须要遍历叶子节点就能够了,不须要遍历整棵 B+Tree 拿到全部的数据)
  3. B+Tree 的磁盘读写能力相对于 B Tree 来讲更强(根节点和枝节点不保存数据区,因此一个节点能够保存更多的关键字,一次磁盘加载的关键字更多)
  4. 排序能力更强(由于叶子节点上有下一个数据区的指针,数据造成了链表)
  5. 效率更加稳定(B+Tree 永远是在叶子节点拿到数据,因此 IO 次数是稳定的)

为何不用红黑树?

红黑树也是 BST 树,可是不是严格平衡的。

必须知足 5 个约束:

  1. 节点分为红色或者黑色。

  2. 根节点必须是黑色的。

  3. 叶子节点都是黑色的 NULL 节点。

  4. 红色节点的两个子节点都是黑色(不容许两个相邻的红色节点)。

  5. 从任意节点出发,到其每一个叶子节点的路径中包含相同数量的黑色节点。

插入:60、5六、6八、4五、6四、5八、7二、4三、49

在这里插入图片描述
基于以上规则,能够推导出:

从根节点到叶子节点的最长路径(红黑相间的路径)不大于最短路径(所有是黑色节点)的 2 倍。

  • 为何不用红黑树?
  1. 只有两路;
  2. 不够平衡。
  3. 红黑树通常只放在内存里面用。例如 Java 的 TreeMap。

索引方式:真的是用的 B+Tree 吗?

在 Navicat 的工具中,建立索引,索引方式有两种,Hash 和 B Tree。

HASH:以 KV 的形式检索数据,也就是说,它会根据索引字段生成哈希码和指针,指针指向数据。

在这里插入图片描述

  • 哈希索引有什么特色呢?
  1. 它的时间复杂度是 O(1),查询速度比较快。由于哈希索引里面的数据不是按顺序存储的,因此不能用于排序。
  2. 咱们在查询数据的时候要根据键值计算哈希码,因此它只能支持等值查询(= IN),不支持范围查询(> < >= <= between and)。
  3. 另一个就是若是字段重复值不少的时候,会出现大量的哈希冲突(采用拉链法解决),效率会下降。
  • 问题: InnoDB 能够在客户端建立一个索引,使用哈希索引吗?

咱们先到官网看看介绍: dev.mysql.com/doc/refman/… InnoDB utilizes hash indexes internally for its Adaptive Hash Index feature 直接翻译过来就是:InnoDB 内部使用哈希索引来实现自适应哈希索引特性。 这句话的意思是 InnoDB 只支持显式建立 B+Tree 索引,对于一些热点数据页, InnoDB 会自动创建自适应 Hash 索引,也就是在 B+Tree 索引基础上创建 Hash 索引,这个过程对于客户端是不可控制的,隐式的。 咱们在 Navicat 工具里面选择索引方法是哈希,可是它建立的仍是 B+Tree 索引,这个不是咱们能够手动控制的。 在 buffer pool 里面有一块区域是 Adaptive Hash Index 自适应哈希索引,就是指这个。

这个开关默认是 ON:

show variables like 'innodb_adaptive_hash_index';
复制代码

从存储引擎的运行信息中能够看到:

show engine innodb status\G
复制代码
----------------------
BUFFER POOL AND MEMORY
----------------------
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
复制代码

由于 B Tree 和 B+Tree 的特性,它们普遍地用在文件系统和数据库中,例如Windows 的 HPFS 文件系统,Oracel、MySQL、SQLServer 数据库。

B+Tree落地形式

MySQL 架构

经过上节课咱们知道,MySQL 是一个支持插件式存储引擎的数据库。在 MySQL 里面,每一个表在建立的时候均可以指定它所使用的存储引擎。

这里咱们主要关注一下最经常使用的两个存储引擎,MyISAM 和 InnoDB 的索引的实现。

MySQL 数据存储文件

首先,MySQL 的数据都是文件的形式存放在磁盘中的,咱们能够找到这个数据目录的地址。在 MySQL 中有这么一个参数,咱们来看一下:

show VARIABLES LIKE 'datadir';
复制代码

每一个数据库有一个目录,咱们新建了一个叫作 checkit 的数据库,那么这里就有一个checkit 的文件夹。

这个数据库里面咱们又建了 5 张表:archive、innodb、memory、myisam、csv。

咱们进入 checkit 的目录,发现这里面有一些跟咱们建立的表名对应的文件。

在这里咱们能看到,每张 InnoDB 的表有两个文件(.frm 和.ibd),MyISAM 的表有三个文件(.frm、.MYD、.MYI)。

在这里插入图片描述
有一个是相同的文件,.frm。

.frm 是 MySQL 里面表结构定义的文件,无论你建表的时候选用任何一个存储引擎都会生成。

咱们主要看一下其余两个文件是怎么实现 MySQL 不一样的存储引擎的索引的。

在 MyISAM 里面,另外有两个文件:

一个是.MYD 文件,D 表明 Data,是 MyISAM 的数据文件,存放数据记录,好比咱们的 user_myisam 表的全部的表数据。

一个是.MYI 文件,I 表明 Index,是 MyISAM 的索引文件,存放索引,好比咱们在 id 字段上面建立了一个主键索引,那么主键索引就是在这个索引文件里面。

也就是说,在 MyISAM 里面,索引和数据是两个独立的文件。

那咱们怎么根据索引找到数据呢?

MyISAM 的 B+Tree 里面,叶子节点存储的是数据文件对应的磁盘地址。因此从索引文件.MYI 中找到键值后,会到数据文件.MYD 中获取相应的数据记录。

在这里插入图片描述
这里是主键索引,若是是辅助索引,有什么不同呢?

在 MyISAM 里面,辅助索引也在这个.MYI 文件里面。

辅助索引跟主键索引存储和检索数据的方式是没有任何区别的,同样是在索引文件

里面找到磁盘地址,而后到数据文件里面获取数据。

在这里插入图片描述

再看看 innodb:

InnoDB 只有一个文件(.ibd 文件),那索引放在哪里呢?

在 InnoDB 里面,它是以主键为索引来组织数据的存储的,因此索引文件和数据文件是同一个文件,都在.ibd 文件里面。

在 InnoDB 的主键索引的叶子节点上,它直接存储了咱们的数据。

在这里插入图片描述

By the way

有问题?能够给我留言或私聊 有收获?那就顺手点个赞呗~

固然,也能够到个人公众号下「6曦轩」,

回复“学习”,便可领取一份 【Java工程师进阶架构师的视频教程】~

回复“面试”,能够得到: 【本人呕心沥血整理的 Java 面试题】

回复“MySQL脑图”,能够得到 【MySQL 知识点梳理高清脑图】

因为我咧,科班出身的程序员,php,Android以及硬件方面都作过,不过最后仍是选择专一于作 Java,因此有啥问题能够到公众号提问讨论(技术情感倾诉均可以哈哈哈),看到的话会尽快回复,但愿能够跟你们共同窗习进步,关于服务端架构,Java 核心知识解析,职业生涯,面试总结等文章会不按期坚持推送输出,欢迎你们关注~~~

在这里插入图片描述
相关文章
相关标签/搜索