转载部分面试精选

MySQL-索引原理

2021-01-18  本文已影响0人  小李弹花

开篇之前,先介绍给大家一个数据结构可视化的网站,里面可以对各种数据结构进行可视化的操作。便于对底层的原理进行理解。

索引的涵义

大家小学时候,都有过使用字典的经历,要查某个字 ,我们一般会通过拼音或者偏旁部首在字典前面的索引页进行查找。查到相关联的字所在的页码,再跳转到相应的页码进行查找。大大增加了查询的效率。

使用字典的场景,就体现了索引的思想。简单的说,索引就是在大量数据里面检索特定数据时,为了提升检索效率,而提供的按某种结构组织的“中间”数据。我们查询时,先去查“中间”数据,然后根据“中间”数据得到最终目标数据的“地址”,去这个地址把我们要找的数据拿出来。

当我们使用MySQL数据库的时候,也是同样的道理,如果表里面有百万级甚至千万级的数据时,如果没有索引,那我们想要检索特定的一行数据,效率将极其低下。

效率低下的本质:大量的IO操作!这里大家一定要注意,如果数据在内存里面,单纯的比对操作是很快的。问题在于每次的查询需要把磁盘数据加载到内存进行计算,这才是我们要用索引的根本所在!

在MySQL数据库里,索引是帮助MySQL高效获取数据的排好序数据结构

  • 索引满足某种数据结构
  • 索引本身是有排序的
  • 索引本质也是数据

索引的数据结构选择

二叉树

举个例子,看下面的这张表,两列字段:

二叉树索引.png

假设我们需要根据第二个字段作为查询条件,查询第一条满足’col2=89‘的数据,如下语句

select * from t where col2=89 limit 1;

上面的例子,体现了使用二叉树作为的索引时,往往能够加快检索效率。但是我们还不能拿二叉树作为MySQL的索引数据结构类型,因为它有致命的缺陷。比如我们用二叉树对第一列(即自增长的id主键字段)建立索引,看看会有什么问题,这了可以在数据结构可视化的网站进行模拟操作,便会得到下面的索引数据:

二叉树的缺陷

大家一眼就能看出问题,由于二叉树结构特点,若子节点大于父节点,则在父节点右边;反之则在父节点左边。我们发现像ID这种单边增长的数据,其二叉树的索引数据结构最终演变成了链表,当根据这个索引进行查找时,本质又是逐条查找。

红黑树

由上面的反例,大家可以看见普通的二叉查找树在极端情况下可退化成链表,此时的增删查效率都会比较低下。为了解决这种情况,就出现了一些自平衡的查找树,比如 AVL,红黑树等。这些自平衡的查找树通过定义一些性质,将任意节点的左右子树高度差控制在规定范围内,以达到平衡状态。
以红黑树为例,红黑树通过如下的性质定义实现自平衡:

节点是红色或黑色。
根是黑色。
所有叶子都是黑色(叶子是NIL节点)。
每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)。

这里就不对红黑树的具体原理做展开讨论,只需要记住一点,它会自动平衡节点左右子树高度差,所以就不会出现上面的那种极端情况。对具体原理感兴趣的朋友可以查看这篇帖子:一文带你彻底读懂红黑树

在这里我们通过可视化的展示,来感性地看看红黑树怎么解决自增长字段的索引问题的:

红黑树

通过上面的动画演示,大家可以发现,当我们试图不断为节点增加右子节点时,红黑树会通过旋转操作,自动平衡节点左右子树的高度。这样一来,就解决了上述的在极端情况下,二叉树退化为链表的情况。

这样一来红黑树就完美了吗?其实不然,它也有缺陷,红黑树的本质还是二叉树的范畴,所以当表里面的数据比较多的时候,树的高度就比较大。比如表里有100万条数据,我们对ID做索引,则树的高度大约是20。(树高的计算方法:2^n=100w,其中的n就是树高),此时查询的效率就比较差了,因为大部分的数据在树的底层,一次查询从根节点逐层比对到目标节点,往往要经历十几次的比对,也就是十几次的磁盘IO操作,注意磁盘的IO操作是个很耗时的动作。极大的影响了查询的性能。

B树 / B+树

MySQL的索引支持两种数据结构,B+树便是其中的一种,也是最常用的。

由上面的二叉树的缺陷,我们比较容易想到,如果要控制树结构的查询效率(比如一次查询的磁盘IO次数控制5次以内),就必须要控制树的高度。当表记录总量不变的情况下,要控制树的高度,那就需要在一个节点存放更多的索引元素,这就是B树。

B树

B树已经能够较好的解决了上面的问题,但是这里我们不打算对B树做过多的讨论。因为精益求精的MySQL研发工程师对B树做了进一步优化,得到了B+树,这才是MySQL的索引结构。

b+树

下面我们对B+树的上述三条性质做详细解释:
由于我们每个节点能够存放的数据容量是有限制的,这个容量限制默认的设置是16k,可以通过下面的语句查看:

SHOW GLOBAL STATUS like 'Innodb_page_size';

由于非叶子结点不存储data,就能存储更多的索引元素,每两个索引元素之间还存储着一个指向子节点的指针(占用的大小为6byte),该指针指向的子节点的所有索引元素≥指针左边的索引元素且<指针右边的索引元素。
现在回顾上面的例子,假设我们用bigint来存储ID字段,bigint占8byte,加上每个指针6byte,则一个ID索引元素占用的存储为14byte,则一个非叶子结点最大可以存储16kb/14b=1170条。
对于叶子节点,不存在指向下一级的指针,省去了6byte,但是里面要存储data,这个data根据不同的MySQL存储引擎,里面可能存放的是索引数据所在行的磁盘存储地址,也可能是索引所在行的其他字段合集。(关于存储引擎,下文会介绍。)这里先假设一个索引的data占1k,则一个叶子节点可以存储大约16个索引字段,在树高为三层的情况下,B+树的结构可以存放的索引总量为1170117016=21902400。现在来看B+树的结构确实很完美,三层的高度,就能容纳千万级的数据索引。同时也可以看到每个节点的默认大小为16k也是非常合适的。

叶子节点之间用指针链接的好处在于,当我们进行区间查找时,效率会大大提高,比如我们要执行如下语句

select * from t where id>15 and id<30;

MySQL会从根节点开始查询,查询到第一个节点,找到满足条件的’ID=18‘的记录,再通过指针跳到第二个节点,继续查找,找到满足条件的’id=20‘的记录。试想如果叶子节点之间没有通过指针相连,那么就要回到上一级的父节点甚至是根节点去继续查找。

下面我们可视化的看下B+树创建索引的过程:

B+树

我们再来根据B+树建立的索引来查找第六行记录:

select * from t where col1=6;

此时会用6来与根节点比较,注意根节点的元素往往是预先加载到内存里面的,经过比较,发现6比根节点所有的元素都大,则直接跳转去它的最后一个子节点,即把第三个子节点的数据加载到内存进行比较,可以很快定位到对应的数据。(这里大家要注意,任何的索引结构形式,都不可能预先把所有索引数据都加载到内存中的)。

上面的例子比较简单,表数据比较少,但是道理是一样的,即使表的数据达到千万级,使用B+树的索引结构,仍然可以把树高控制在3-5层,所以至多只需要4-5次的io操作即可检索到所求的记录。

Hash表

MySQL的索引支持两种数据结构,Hash便是其中的一种,在一些特殊的场景有其独特优势。

Hash(散列、杂凑)函数: 是将任意长度的数据映射到有限长度的域上。直观解释起来,就是对一串数据m进行杂糅,输出另一段固定长度的数据h,作为这段数据的特征(指纹)。
也就是说,无论数据块m有多大,其输出值h为固定长度。比如我们常用的MD5,SHA等都是Hash函数。

哈希表是根据设定的哈希函数H(key)和处理冲突方法将一组关键字映射到一个有限的地址区间上,并以关键字在地址区间中的象作为记录在表中的存储位置,这种表称为哈希表或散列,所得存储位置称为哈希地址或散列地址。作为线性数据结构与表格和队列等相比,哈希表无疑是查找速度比较快的一种。

当我们对表字段建立Hash结构的索引时,MySQL会对字段的值做Hash运算,把得到的散列值连同该行记录磁盘地址存到hash映射表:

Hash

Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以Hash索引的查询效率要远高于B-Tree索引。

但是Hash索引也有其缺陷:

存储引擎

MySQL数据库里面的数据最终是以文件的形式存放在磁盘上,就和我们磁盘上的其他文件一样,不管是word文件还是文本文件,都需要一种存储方式。这就需要存储引擎,它的功能就是按照一定的规则在磁盘上组织文件。

MySQL支持多种存储引擎,其中MyISAM和InnoDB是最常见的两种。

这里通过建立两张表举例,一张表名叫teacher,使用MyISAM存储引擎;一张表名叫student,使用InnoDB存储引擎;我们可以进入到mysql安装目录下,去看下这两张表对应的文件:

# ls -l
total 144
-rw-rw---- 1 mysql mysql     65 Jan 21 02:27 db.opt
-rw-rw---- 1 mysql mysql   8586 Jan 21 05:54 student.frm
-rw-rw---- 1 mysql mysql 114688 Jan 21 05:54 student.ibd
-rw-rw---- 1 mysql mysql      0 Jan 22 01:38 teacher.MYD
-rw-rw---- 1 mysql mysql   1024 Jan 22 01:38 teacher.MYI
-rw-rw---- 1 mysql mysql   8586 Jan 22 01:38 teacher.frm

可以看到:

MyISAM

  • .frm : 是frame的缩写,意为“框架、结构”,这个文件存储表的结构定义;
  • .MYI : 是MyISAM Index的缩写,这个文件存储表的索引数据;
  • .MYD :是MyISAM Data的缩写,这个文件存储表里面的内容数据;

使用MyISAM作存储引擎,索引文件和数据文件是分离的,所以这种索引也叫做非聚集索引

MyISAM存储.png

非聚集索引的叶子节点里面的data存放的是行记录的磁盘存储地址,这样索引数据比较轻量,但根据索引查询时,并不能直接得到结果,需要根据索引得到的记录地址,去额外进行一次IO操作。

InnoDB

  • .frm : 是frame的缩写,意为“框架、结构”,这个文件存储表的结构定义;
  • .ibd : 是InnoDB Data的缩写,这个文件存储表的索引和数据;

InnoDB表有如下几个性质:

  • InnoDB表必须有主键,并且推荐使用整型的自增主键
  • 表数据文件本身就是按B+树组织的一个索引结构文件
  • 聚集索引-叶节点包含了完整的数据记录
  • 辅助索引结构叶子节点存储的是主键值

为什么InnoDB表必须有主键? 有人可能会说:“我在建表的时候,没有指定主键,照样创建成功了啊”。你可能不知道,如果没手动建主键 ,mysql会找一列唯一数据的列当做主键,或者加一列主键(mysql自动加的主键列是你看不到的)。并以此主键字段将表数据按照B+树组织起来:

主键索引

为什么要整型自增? 不妨假设用32位的UUID作为主键,我们来看看效果:

主键非自增的缺陷

上面的动画可以看到,新增的主键值8,按照大小排序的规则,插入了最后一个叶子节点的第二个位置,由于达到了该叶子节点的存储上限,则B+树根据内部的优化规则,进行了树的重构,不仅叶子节点的布局发生了变化,树的高度也发生了变化,这是很耗性能的。

索引和数据是在一个文件中组织的,所以这种索引也叫做聚集索引。上面讨论的主键索引就是聚集索引。
由于聚集索引只在一个ibd文件中操作,所以效率更高。

辅助索引(Secondary Index)也叫 二级索引非主键索引 ,它跟主键索引很相似,唯一的不同就是叶子节点的data部分存储的不是该行的记录值,而是对应的主键值。

辅助索引.png

辅助索引的叶子节点这么设计有两点好处:

辅助索引的缺点也很明显:

InnoDB对Hash的支持

由上面的Hash索引结构,我们知道,Hash表里面的值存放的是行记录对应的磁盘存储地址,所以它的索引文件和数据文件是分开的。而InnoDB存储引擎的设计,是将索引和数据存放在同一个文件中。所以使用InnoDB作为存储引擎时,我们是无法创建Hash结构的索引的,此时只能用B+树;

但是我们上面有说了,对于某些特殊的场景,Hash的性能是优于B+树的,鱼和熊掌就不可兼得么?
其实不然,mysql的InnoDB存储引擎是支持Hash索引的,不过它的Hash索引是自适应的,就是说InnoDB会根据表的使用情况自动为表生成Hash索引,这个过程是不能被认为干预的。

联合索引

未完待续。。。

上一篇 下一篇

猜你喜欢

热点阅读