一.首先什么是数据库的索引?
很多人都说索引就像一本书的目录,当我们想要查看一本书具体的某一页的某一段内容时,可以先去查看这本书的目录,从而快速的定位这个内容在哪一页,这样就可以加快我们查询的速度,否则我们要一页一页的翻阅,才能定位到我们想要获取的内容,这样查询效率就会被降低。
同样的,数据库的索引也是为了增快我们查询的速度,它是为了增快我们查询数据的排好序的数据结构,这里有一个概念,它实则是一个数据结构,想象一样,我们有如下的一张表:
id | name |
---|---|
1 | 张三 |
2 | 李四 |
3 | 王五 |
4 | 赵六 |
5 | 田七 |
6 | 冯八 |
7 | gsp |
8 | wj |
9 | tk |
如果我们想要执行一个操作 select * from table where id = 6; 那么在没有索引的情况下底层是怎么查询的呢?它肯定是一行一行的比对,首先查到第一条id = 1的数据,发现条件不对,然后再比对第二条数据发现还是不对,以此类推。。。一直比对到 id = 6,发现对了,于是将结果返回,这里发现,他一共比对了6次,那么问题来了,如果我们这的数据是上百万条上千万条呢?这样的一条一条的比对下去难免性能和查询速度都会非常的低效。
这个时候索引就派上了用场,我们可以通过索引来过滤一些我们不必要的比较,以及快速的定位我们想要查询的数据的具体位置。下面就来看看常见的索引结构以及它是如何快速的查找数据的。
二.常见的索引结构
常见的索引结构有:1.二叉排序树 2. 红黑树 3. hash表 4. b+树
1.首先来看看如果是二叉排序树它是如何建立索引快速查询的,下面有一张表,右边是为这张表的Col2字段建立的索引,索引的每一个节点相当于是一个key-value的结构,key为Col2的列值,value为指向其在磁盘中存放的地址值。
我们还是来思考这样的一个需求:倘若我们要执行 select * from table where Col2 = 89 ; 在没有建立索引之前我们是从上到下依次比较,我们需要比较6次才能返回结果,而当我们建立索引之后,首先我们会先去索引结构的根节点进行比较 34 < 89 ,查询二叉树的右子树,发现他的右儿子就是89 ,那么直接根据value保存的地址值去取数据返回,这样一来建立索引之后我们只需要比较两次就得到了我们希望的答案,是不是加快了速度!
但是遗憾的是我们的mysql底层默认索引机制却不是二叉排序树,why?是因为在特定的情况,使用二叉排序树的性能并没有得到有效的提高,比如,当我们在上述表的Col1字段上建立索引,那么根据字段插入的顺序,此时的索引结构是这样的:
这个时候我们就会发现,此时的索引查找和原来的没有索引时的逐条查找没有什么区别,他们都需要6次才能定位到 第六条数据,因此这就是它的劣势所在!
2. 那么红黑树的这个结构性能怎么样呢?首先在上述的这个场景中,红黑树确实是性能提高了很多,如果我们的还是通过红黑树来给Col1建立索引,他们根据数据的逐条插入后索引的结构会是这样的:
可以明显发现,红黑树是一个二叉平衡树,它可以根据我们的树结构来时刻的保持平衡,并且他还是保持了树的顺序性,那么还是查找 id = 6 这条数据时他只用了3次比较,因此它很好的解决了字段顺序性的问题。但是很遗憾,他也不是mysql的默认索引机制!因为他也有它的弊端,因为这种结构她很受数据量的影响,虽然它能够做到自动平衡,但是一旦数据量上升,必然会增加数据的高度,倘若我们要查询的数据恰巧就在叶子节点上,那么我们势必搜索的次数就是树的高度,倘若这个树很高,那么比较的次数也会很多,而且红黑树在做树的平衡时也会有性能的消耗。
3.再来看看我们的hash表存储结构能否解决上述的难题,我们的hash思想是将每一个字段值进行hash运行得到一个值,再将这个值和数据具体的磁盘位置给合体映射成一张表,这样我们需要查找一个特定的值时,如 select * from table where Col2 = 87时我们只需要将87进行一个hash,这样就能快速定位到数据的位置,那么这种结构的不足在哪呢,就是对于范围查询不是很好,如 select * from table where Col2 > 87 ,这样的话怎么hash呢?所以这种方式我们用的很少。
4.说了前面的这些数据结构的劣势之后,真正的主角要登场了,mysql的默认索引机制其实是采用的是B+树的结构,它是根据红黑树和b树的缺点演变而来,我们之前提到红黑树的缺点就是当数据量一旦增大之后树的高度也会随着增加,那么B树其实就是通过横向扩展限制了树的高度,以至于减少比对的次数,横向扩展简而言之就是将原来存储一个数据的节点来存储多个数据,这样每个节点保存的数据多了,那么树的高度就会减少了,我们可以控制树的高度,只需把数据都存在一个节点中就好,b树的结构是这样的:
可以看得出来,我们的b树其实就是从二叉树转变成了多叉树,b树还是保存了这种二叉顺序树的特性,只是每个节点不在只保存一个元素了,然而Mysql的工作人员还是对B树做了相应的改进,从而有了B+树,其结构如下图:
可以看出我们的b+树的叶子节点包含了我们数据表的所有索引元素,并且将一些处在中间节点的数据给提到非叶子节点进行冗余保存,其中非叶子节点不存储具体的磁盘位置值了,只存储具体的索引字段值,那么为什么这么做呢?是因为我们的树结构中的每一个节点都是有默认大小的,默认是16k,一旦我们的节点不存储data了,那么他就可以存储更多的索引元素值了,我们每次去读取这个索引文件的时候都是将磁盘中的数据给加载到内存中,每读取到一个节点,都会把那个节点的所有数据都加载到内存中(其中根节点一般都会实现加载),这样在内存中进行查询的速度是非常块的,所以如果我们的树高为3,也只需要3次磁盘的IO,查询的速度是非常快的。B+树的第二个优化在于在叶子节点间还增加了指针,这样在进行范围查询的时候,如:select * from Col2 > 20的时候根据指针就能快速的返回结果,不需要从根节点再次遍历,这样就解决了b树的范围查询的不足。