众所周知,我们在使用MySQL时,当数据量较大时,会在一些常用的查询条件字段上添加索引,以此来优化查询性能。但是大家是否知道索引为什么能够优化查询性能呢?以及MySQL的索引是一种什么样的数据结构,能够如此高效的优化查询语句?下面我将和大家一起聊一聊MySQL索引的一些知识。
MySQL索引结构
MySQL在创建索引时,选择的B+Tree作为索引的数据结构。那么B+Tree是一种什么样的数据结构呢,以及B+Tree为什么能够提高查询性能呢?这就引入了两个问题:
- B+Tree:是一种有序的多叉树的树形结构。每个节点可以存储一个或多个元素,每个元素又有一个左子树的指针和右子树的指针。并且只有叶子节点存储具体的元素,非叶子节点只存储索引值。节点存储的元素个数称之为度(Degree)。下面我们看一下Degree=3时,B+Tree的数据结构。
假设有如下九个元素: (K,V)格式,其中K为索引值,V为节点数据。
(5,data1), (8,data2), (6,data3), (4,data4) , (2, data3), (12, data6), (67,data7),(7,data8), (9,data9)]
以上数据,存在在B+Tree中的结构如下图所示:
结合上图B+Tree总结一下其结构特点:
- 树上的每个Node节点可以存储多个元素。
- 每个元素都有一个指向左子树(比它小)的指针和右子树(比该元素大)的指针。
- 只有叶子节点存储实际的数据,其他非叶子节点只存在索引数据。(提高内存使用率,下面会讲到)
- 叶子节点从左往右是连续的,即叶子节点时通过指向右侧兄弟节点的指针将所有的叶子节点都串联起来。这样一旦索引到每个值之后,就可以查到所有比该值大的元素。
- 下面回到第二个问题:B+Tree为什么能够提高搜索性能?
B+Tree提高查询性能主要基于如下两点:
- 结合上图很容易得出这个结论,B+Tree通过对索引做排序,空间换时间,建立起一颗B+Tree后,可以根据索引字段进行二分查找,时间复杂度为O(lgn)。从而能够更快的定位到元素,从而极大的提升查询效率。
- 减少IO:MySQL的数据文件是存储在磁盘上的,索引文件会被导入内存,而内存的读取是以页(一段连续的内存空间)为单位进行读取的,B+Tree的一个节点可以同时存储多个元素,从而一次IO就可以读取到多个元素,从而减少IO,提升查询效率。
MySQL的其他索引结构
学过数据结构的人都知道,实现O(lgn)的时间复杂度有很多种数据结构,如二叉树,大名鼎鼎的红黑树,以及和B+Tree齐名的BTree等等,为什么MySQL偏偏选择了B+Tree呢?下面我就和大家一起分析一下各个数据结构的优势,以及存在的问题。一共分析如下4种数据结构的优劣:二叉树
,红黑树
,hash表
,BTree
- 二叉树:二叉树是一种最简单的树形数据结构,其特点是:
- 左子树的节点的值小于当前节点
- 右子树的节点的值大于当前节点
以上面的数据为例,插入上述元素后,二叉树的结构如下所示:
二叉树的优点:
- 结构简单,易于实现
- 一定情况下,能够保证索引事件复杂度为O(lgN)。
二叉树的缺点
- 容易造成数据倾斜
- 当数据量大式,树的深度会很深。此时会进行多次IO操作,极大的影响性能。
- 红黑树:也是一颗二叉树,通过对节点的平衡转换,最终会形成一颗平衡二叉树。红黑树的具体细节不做过多赘述,有兴趣的小伙伴可以自行网上查阅相关资料。
红黑树的优点:
- 查询时间复杂度能够保证O(lgN)。
- 解决了二叉树数据倾斜的问题。
红黑树的缺点:
- 当数据量很大时,树的深度依然会很深,没有办法解决IO的问题
- Hash表:Hash表是一种数组+链表的数据结构,Java中的HashMap就是Hash表的典型实现。JDK8中的HashMap采用数组+链表+红黑树的结构实现Hash表,在一定情况下能够实现O(1)的查询性能。
Hash表首先会对索引的值进行hash,获取到hash值,然后和当前数组取模,得到数组的索引。然后将对应的元素放在该索引对应的数组元素中(如果有hash冲突,需要放在数组元素对应的链表上)
还是以上面的 数据为例,存储的结构图如下所示:
注意:此处的示意图只作为演示,实际过程中,当元素个数超过阈值(默认数组长度*0.75)时,就会触发扩容。
Hash表的优点:
- 结构简单,易于实现。
- 等值匹配可以达到近似O(1)的时间复杂度。效率很高
Hash表的缺点
- 由于Hash表是对索引元素的hash值取模,进行存储的。因此丢失了大小。不适用于范围查询:如select * from table_a where age>24;
- BTree:BTree和B+Tree结构类似,最大的区别是有如下哦两个,
- BTree在所有的节点上都会存储数据,而B+Tree只在叶子节点上存储真实的数据。
- BTree的叶子节点之间没有通过指针关联,而B+Tree的所有叶子节点通过一个指向右边兄弟节点的指针进行了关联。
还是以以上的数据为例,BTree的结构如下所示:
由上图可知,BTree解决了上面二叉树和红黑树的问题,即数据倾斜和数量变大时,树的深度膨胀的问题。当时也存在如下两个缺点:
- 每个节点都需要存储数据元素,导致单个节点可存储的元素个数非常有限。
- 叶子节点没有通过指针进行关联,在处理范围查询时,需要多次查询索引结构。
- BTree和B+Tree对比
首先看BTree的第一个缺点,在B+Tree是如何解决的:
例如,MySQL一次读取4M(1024*1024byte)的内存空间。
一个索引节点大概8byte+左右两个指针16byte,一共20byte。
数据节点通常较大,加入200byte。
- 如果采用BTree,则一页只能存储1024*1024/(20+200)个索引元素
- 如果采用B+Tree,则一页可以存储1024*1024/20个索引元素。从而可以极大的减少IO
其次,针对BTree的第二个不足,范围查询的问题,B+Tree也进行的相应的处理。B+Tree的叶子节点之间相互关联,从而查询但第一个索引节点后,就可以通过叶子节点的指针依次往下执行,而不需要多次查询索引文件。
SQL性能优化
通常的SQL性能优化都是通过索引来优化的。但是索引通常并不是能够解决一切问题。SQL性能优化的最终目标归根结底其实是减少数据库的回表查询。