散列表(也称哈希表)是根据关键码值(Key value)而直接进行访问的数据结构,它让码值经过哈希函数的转换映射到散列表对应的位置上,查找效率非常高。哈希索引就是基于散列表实现的,假设我们对名字建立了哈希索引,则查找过程如下图所示:
对于每一行数据,存储引擎都会对所有的索引列(上图中的 name 列)计算一个哈希码(上图散列表的位置),散列表里的每个元素指向数据行的指针,由于索引自身只存储对应的哈希值,所以索引的结构十分紧凑,这让哈希索引查找速度非常快!但是哈希索引也有它的劣势,如下:
在 InnoDB 引擎中,有一种特殊的功能叫「自适应哈希索引」,如果 InnoDB 注意到某些索引列值被频繁使用时,它会在内存基于 B+ 树索引之上再创建一个哈希索引,这样就能让 B+树也具有哈希索引的优点,比如快速的哈希查找
跳表,是基于链表实现的一种类似“二分”的算法,它可以快速的实现增,删,改,查操作。
简单地说,跳表是在链表之上加上多层索引构成的
先来看一下单向链表如何实现查找
当我们要在该单链表中查找某个数据的时候需要的时间复杂度为O(n)
如果我们给该单链表加一级索引,将会改善查询效率
如图所示,当我们每隔一个节点就提取出来一个元素到上一层,把这一层称作索引,其中的down指针指向原始链表。
当我们查找元素16的时候,单链表需要比较10次,而加过索引的两级链表只需要比较7次。当数据量增大到一定程度的时候,效率将会有显著的提升
跳表的查询时间复杂度可以达到O(logn)
如果我们再加多几级索引的话,效率将会进一步提升。这种链表加多级索引的结构,就叫做跳表
平衡多路查找树
what ??? 平衡@#¥%树? 稳住,能赢!!!
首先我们先看一下什么是平衡二叉树?
平衡二叉树又称为AVL树,是一种特殊的二叉排序树
emmmmm..... 什么又是二叉排序树(BST树)????
我们来看一下二叉排序树的特性(⚠️ 二叉排序树可以是空树):
好,了解完特性,我们来看看二叉排序树到底长什么样字?
二叉查找树可以任意地构造,同样是2,3,5,6,7,8这六个数字,也可以按照下图的方式来构造:
但是这棵二叉树的查询效率就低了(二叉排序树越矮查找效率越高)。因此若想二叉树的查询效率尽可能高,需要这棵二叉树是平衡的,从而引出新的定义——平衡二叉树,或称AVL树
AVL树(平衡二叉树)有以下特性:
一句话概括就是:平衡二叉树是一种特殊的二叉排序树,以树中所有结点为根的树的左右子树高度之差的绝对值不能超过1
最终,平衡二叉树它是长这样的:
好,知道了二叉排序树(BST树),平衡二叉树(AVL树)后,我们看一下主角 B-树 (平衡多路查找树)
B-树中,含有最多分支数的结点,该结点分支树的总数就是B-树的阶,通常用m表示,从查找效率考虑,要求m>=3
一棵m阶的B-树或者一棵空树,或者是满足以下要求的m叉树
了解了B-树的特性后,是不是很好奇B-树长啥挫样?呐~看下面:
没错,这就是一个3阶的B-树(为什么是3阶你明白了吗?)
通过上面的图我们可以看到:
模拟查找关键字29的过程:
- 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
- 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
- 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
- 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
- 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
- 在磁盘块8中的关键字列表中找到关键字29。
从上面的过程可以看出,整个 查找需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree(平衡二叉树)缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率
通过上面的图,有个概念叫磁盘块
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么
那什么叫页(Page)呢?
页是InnoDB存储引擎中的概念,页是其磁盘管理的最小单位,InnoDB存储引擎中默认每个页的大小为16KB
在计算机里,无论是内存还是磁盘,操作系统都是按页的大小进行读取的,磁盘每次读取都会预读,会提前将连续的数据读入内存中,这样就避免了多次 IO,这就是计算机中有名的局部性原理
即我用到一块数据,很大可能这块数据附近的数据也会被用到,干脆一起加载,省得多次 IO 拖慢速度,以此提高查询效率
这个连续数据有多大呢,这个连续数据就是 MySQL 的页,默认值为 16 KB,页大小并不是越大越好,InnoDB 是通过内存中的缓存池(pool buffer)来管理从磁盘中读取的页数据的页太大的话,很快就把这个缓存池撑满了,可能会造成页在内存与磁盘间频繁换入换出,影响性能
B+树是在B-树的基础上的一种优化,
使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构
B+树相对B-树有几点不同:
下面看一下B+树的容颜:
B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个结点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子结点上,而非叶子结点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度
InnoDB存储引擎中,一个16KB大小的页可以存多少条数据呢?
一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页,中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为〖10〗^3)。
也就是说一个深度为3的B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条记录
B+ 树为了维护索引的有序性,每插入或更新一条记录的时候,会对索引进行更新
以3阶B+树为例,
假设当某个结点的关键字总数为3(以达到最大关键字数量),如果恰好需要再往该结点上插入关键字时,显然就不再符合B+ 树条件,这时就会造成页分裂,以调整这个节点以让它符合B+ 树条件。页分裂造成的调整必然导致性能的下降
什么时候会发生页合并呢?答案是在删除记录的时候,当删除表记录的时候,索引也要删除。此时就有可能发生页合并
结合B+Tree的特点,自增主键是连续的,在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率
以上就是关于散列表,B-树,B+树的总结,如有错误之处,欢迎大家指正,讨论!
【参考文档】
1. https://mp.weixin.qq.com/s?__biz=MzI5MTU1MzM3MQ==&mid=2247484006&idx=1&sn=3e15abeb5299a3e9b578332dd8565273&scene=21#wechat_redirect
2. https://blog.csdn.net/sinat_32176267/article/details/85460695
3. https://www.cnblogs.com/liqiangchn/p/9060521.html
4. << 数据库结构高分笔记 >>