持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
索引是存储引擎用于快速查找记录的一种数据结构。
可以把数据库形象地理解为一本字典,而索引就是这本字典的目录,通过索引查找数据的过程对应起来就是通过目录查找对应字的过程,那么自然而然就想到它可以使用key-value格式的数据结构
那应该用什么数据结构呢?索引是在存储引擎层的,而不是在服务器层,所以不同的存储引擎索引的实现方式不同,而InnoDB采用的是B+树。既然是key-value格式的结构,可以使用hash、二叉树、B树、B+树
那为什么要用B+树而不用其他的呢?这里我想先铺垫一下底层相关的知识,因为不管是索引还是数据,最后都是要存储在磁盘中的而不是内存中,从磁盘中读取数据,就涉及到磁盘和内存之间的交互,就涉及到局部性原理和磁盘预读。局部性原理又分为时间局部性和空间局部性。时间局部性指的是之前被查询的数据很快可能被再次查询;空间局部性原理指的是数据和程序都有聚集成群的倾向,具备某一个特点的数据经常聚集存储。磁盘预读:磁盘跟内存在进行交互的时候有一个最基本的逻辑单位,称之为页,每次进行数据读取的时候,读取的是页的整数倍,页的大小跟操作系统相关,默认4k,8k,在MySQL的innodb存储引擎中默认16kb。这就会存在一个问题,一个表,落地到磁盘,有可能非常大,所以不可能把数据从磁盘一股脑全部读取到内存中,所以每次进行数据读取的时候分块读取,每次读取一部分数据,然后根据指针再磁读取下一块信息。我们选择用什么数据结构作为数据库的索引的时候,需要考虑尽可能少的减少IO次数,能读一次就绝对不读两次,也就是尽可能少的读取数据,从而减少整体IO
如果用哈希表来实现索引,存在三个问题:
1、必须要设计一个优秀的哈希算法,保证数据足够散列,如果存在大量的哈希冲突,或者哈希碰撞,那么导致某些查询效率非常低
2、当需要进行范围查询的时候,需要挨个对比每一个元素值,效率极低,在生产环境中大部分查询是范围查询的
3、哈希表比较浪费内存,而内存是宝贵的资源
memory存储支持哈希存储
innodb支持自适应哈希(系统自动判断用哈希索引还是B+树索引,无法人为干预)
如果使用二叉树或者二叉搜索树来实现,因为前面说的局部性原理,所以实际生产环境中数据的插入极有可能会导致树的深度过深,也就是倾斜现象,最终二叉树会退化成类似链表的结构,从而造成IO次数变多
平衡树的话,保证了最长子树和最短子树不超过1,查询的时间复杂度为O(log2n),它一定程度上缓解了树深度过深的问题,但是无法本质性地解决这个问题,因为数据多了,树的总深度依然很深,并且,旋转的过程非常耗费时间,从而导致插入和删除效率极低,只是查询效率高
红黑树的话通过旋转和变色,将插入和查询性能进行了平衡。
但是不管是平衡树还是红黑树,它们都没有利用局部性原理和磁盘预读。而平衡树和红黑树由于逻辑上很近的地节点物理上可能很远,所以每次进行磁盘预读的时候并没有充分利用磁盘预读的功能。然后又由于深度大,进行磁盘IO操作更多一些。
前面讲到的树结构不适合做索引的共同特点是节点有且只有两个分支,所以一旦数据变多,树的深度一定会增加,导致IO次数过多,从而影响读取效率。既然不能变高,就让他变胖,从而存储更多数据;于此同时,我们把每个节点存储多个关键字,节点大小设置为磁盘页的大小,这样子充分利用了磁盘预读的功能。所以我们开始考虑,引入一个有多个分支的数据结构,也就是B树,它有N多个分支,由每个节点由值、指针和数据组成,通过读取三个磁盘块,也就是3次IO即可读取到整个B树的数据,我们可以来计算一下用B树来作为索引的数据结构3次IO能读取多少数据。为了简化,假设只有数据占用空间,主键和指针不占用空间,一个数据1k,当前磁盘最多存储16条数据,也就是16K,那么三次IO最多能读取到的数据就是16 * 16 * 16;进行数据读取之后,就是在内存中对数据进行查询操作,而上面的平衡二叉树,主要是通过磁盘读取的。虽然B树查询查询的次数不比平衡二叉树的次数少,但是相比起磁盘IO速度,内存中比较的耗时就可以忽略不计了。因此,B树更适合作为索引。
这时候我们考虑能否优化B树,我们发现,最本质的原因是叶子节点存储了数据,数据占空间太大,导致一个磁盘块能获取的数据太小了,所以有了B+树结构作为索引,它的本质就是有序数组链表+平衡多叉树。它的非叶子节点只存储key和指针,可以尽可能多的存储key和指针,叶子节点存储数据,这样子比方说一磁盘块能存储16kb数据,那么三层的结构,通过3次IO就能获得1600* 1600* 16个数据
二叉树、BST、AVL、红黑树:他们都是有且仅有两个分支,后面三个是有序的;后面两个保证左右两个分支的数量尽可能相等,AVL是严格平衡树,红黑树是非严格平衡树。这些树有一个最大的通病,每一歌分支有且仅有两个分支,当需要向其中插入更多数据的时候,就必须增加树的高度,而增加树的高度会导致树变深,导致IO的次数变多,所以所有的二叉树都是不行的
B+树假设非叶子节点的话,16Kb* 1024个字节的容量,假设每个节点的数据量是10字节,那么非叶子节点能存储16* 1024/10个数据,叶子节点能存储16个数据,所以三次IO能读取的数据是(16* 1024/10)^2*16