说到数据库索引我们都比较熟悉,比如当数据查找很慢,查询效率低下,就建个索引,提升效果就很明显,但也有时效果不是很好,今天我们首先就来聊聊索引的一些原理性知识,索引是数据库最重要的概念之一,索引存在的目的就是提高数据查找效率,类似书的目录。
索引模型
索引的目的是为了提高查找效率,但是构建索引的方式有多种,索引就引入了索引模型的概念。用于提高读写效率的数据结构有很多,先介绍三种相对简单的数据结构:散列表(又称哈希表 hash table)、数组、搜索树。
而索引的构建是在数据库引擎层做的,所以不同的引擎有不同的构建方式,由于InnoDB是使用范围最广的mysql引擎。我们还是以InnoDB来分析索引模型。
哈希表
首先以哈希表为例,如果数据库引擎以哈希表作为底层数据结构来存储数据,用hash函数把key计算成具体的位置,然后把value值放到这个位置,通过key来查找value。
不可避免的是多个key值经过hash运算可能会生成同一个位置信息,这种现象我们称为hash冲突,解决hash冲突的方法有很多,比较典型的一种处理手段是针对生成的相同的哈希值,我们新建一个链表来将这些原始数据链接起来,如图所示。 0c62b601afda86fe5d0fe57346ace957.png

当需要查询身份证号为ID_card_n2的姓名时,首先将通过hash函数将ID_card_n2生成N(数组地址信息),然后将生成的N作为地址下标在数组中查询对应的值,发现值为链表对象。说明数组中第N个元素用链表存入了多个用户信息。接着循环遍历查找链表中每个节点的ID_card是否等于ID_card_n2,如果等于就取出对应的用户信息,得到用户姓名。
因为哈希表中存储的数据不是按照ID_card递增存入的,所以当需要按照ID_card区间(ID_card_x,ID_card_y)来查询有哪些学生时,只能进行全表扫描。
所以hash表只适合等值查询的场景
数组
而数组在等值查询和范围查询时的性能都非常不错。还是以身份证号查用户姓名为例。如果我们用数组来实现的话,示意图如下所示:

这时如果你需要查询ID_card_n2的姓名,通过二分查找能快速找到,很显然这个结构也支持范围查找。
如果仅仅看查询效率有序数组就是最好的数据结构了。但是在需要更新数据的时候就麻烦了,当需要往数组某个位置中插入一条数据时,该位置往后的元素都需要往后移动来腾出这个位置。这也是最基本的数据类型(数组和链表的一个明显的对比),数组的修改效率很低,而要找到这个元素效率很高,时间复杂度为O(log(N)),而链表恰恰相反。
所以有序数组索引只适合静态的存储索引,不适合再继续往里面添加元素。
二叉树
二叉树也是一种非常典型的数据结构,最重要的特征就是每个节点最多有两个子节点,其中左子节点小于父节点,右子节点大于父节点,所以当要找到指定元素的时间复杂度也是为O(log(N)) ,如果你要查 ID_card_n2 的话,按照图中的搜索顺序就是按照 UserA -> UserC -> UserF -> User2 这个路径得到。如图所示:

为了维持二叉树的查询效率,当对二叉树节点进行删除和更新的时候,也要保持为平衡二叉树,时间复杂度也是O(log(N))。
二叉树是搜索效率最高的,但是数据库一般都不使用二叉树,因为索引不止存在内存中,还需要写到磁盘上。
你可以想象一下有20层的二叉树,约为100万个元素,依次查询可能需要访问20个数据块,在机械硬盘时代,从磁盘随机读取一个数据块的寻址时间为10ms。所以如果用二叉树来存储一个100万行的表,查单独访问一行可能需要20个10ms的时间。
为了减少查询时间,就是尽可能少读取磁盘,访问过少的数据块,所以就需要让数中每层存储尽可能多的数据,就需要用N叉树来存储,这里,“N叉”树中的“N”取决于数据块的大小。
N叉树由于在读写上的性能优势,以及适配磁盘的访问模式,已经被广泛应用于数据块引擎中了。
数据库底层存储的核心就是基于这些数据模型的。每碰到一个新数据块我们需要先关注他的数据模型,才能从理论上分析它的适用场景。