MySQL索引底层数据结构
索引的本质
- 索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。
- MySQL的B+树索引模型是帮助其高效获取数据的排好序的数据结构。
常见索引模型
索引的出现是为了提高查询效率,但是实现索引的方式却有很多种,这里主要给你介绍三种常见的数据结构:哈希表、数组、搜索树。
1.哈希表
哈希表的查询时间复杂度是O(1),但是哈希表的缺陷是不支持范围查询的,当sql中涉及到范围查询时,哈希表的效率就会很低,只能扫描全表来判断了。
所以,哈希表这种结构适用于只有等值查询的场景,比如 Memcached 及其他一些 NoSQL 引擎。
而有序数组在等值查询和范围查询场景中的性能就都非常优秀
2.有序数组
有序数组的查询可以使用二分,时间复杂度是O(logn)。如果只看查询有序数组性能很高,但如果涉及到更新,你往中间插入一个记录就必须得挪动后面所有的记录,成本太高。
所以,有序数组索引只适用于静态存储引擎,比如你要保存的是 2017 年某个城市的所有人口信息,这类不会再修改的数据。
3.搜索树
3.1 二叉搜索树
二叉搜索树的特点是:父节点左子树所有结点的值小于父节点的值,右子树所有结点的值大于父节点的值。基于此二叉搜索树的查询时间复杂度为[O(logn), O(n)]。当然为了维持 O(log(N)) 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 O(log(N))。
3.2 多叉搜索树
多叉树每个节点都可以有多个子节点,每个节点的子节点数量上限为m。每个子节点都保证从左到右递增。时间复杂度m*O(logn) 近似O(logn)
二叉树是搜索效率最高的,但是实际上大多数的数据库存储却并不使用二叉树。其原因是,索引不止存在内存中,还要写到磁盘上。
这里举个例子,假设一棵 100 万节点的平衡二叉树,树高约 20。机械硬盘中每一次磁盘IO大约10ms,也就是说最坏情况需要20×10ms才能查询到数据。非常的慢了。
为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。那么,我们就不应该使用二叉树,而是要使用“N 叉”树。这里,“N 叉”树中的“N”取决于数据块的大小。
以 InnoDB 的一个整数字段索引为例,一个数据页16KB,bigint占8B,指针占6B。所以N = 16KB / (8B + 6B) = 1170个,假设树高 h = 3,大约就可以存 1170^3 大约 16亿个数据了。考虑到树根的数据块一直在内存,实际就2次磁盘IO就能找到数据了。(其实第二层大概率也在内存)
多叉树由于在读写上的性能优点,以及适配磁盘的访问模式,已经被广泛应用在数据库引擎中了。
3.3 B树
B树是一种改进的多叉树
- 叶节点具有相同的深度,叶节点的指针为空
- 所有索引元素不重复
- 节点中的数据索引从左到右递增排列
由于每一层的索引都会存储具体的数据,所以B-树的树高会比B+树高,导致更多的磁盘IO。
3.4 B+树
B-树的变种:
- 非叶子节点不存储data,只存储索引(冗余),可以放更多的索引
- 叶子节点包含所有索引字段
- 叶子节点用指针连接,提高区间访问的性能
InnoDB 的索引模型
在 InnoDB 中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。又因为前面我们提到的,InnoDB 使用了 B+ 树索引模型,所以数据都是存储在 B+ 树中的。
每一个索引在 InnoDB 里面对应一棵 B+ 树。
主键索引示例图:
普通索引示例图:
主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)。
普通索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。
主键索引和普通索引查询的区别就在于,普通索引只能得到主键ID,再使用主键ID回表去主键索引树找到对应的数据。而主键索引可以直接找到对应数据。
也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。
普通索引为什么不存储完整记录
- 数据一致性考虑:减少一致性方案复杂度
- 空间考虑:不可能每颗索引树都冗余存储所有数据
聚簇索引和非聚簇索引
- 聚簇索引表示叶节点包含了完整数据记录。
- 非聚簇索引则表示索引和数据行不属一个物理文件中,如MyISAM引擎使用的就是非聚簇索引。索引中保存的是数据文件中的地址,需要重新定位拿到数据。
索引维护
B+ 树为了维护索引有序性,在插入新值的时候需要做必要的维护。如果ID是自增的那么就只需要在记录后面插入新值,如0011后插入0012。如果ID不是自增的,那么就需要找到合适的位置插入新值。如果插入0007那么就比较麻烦了,需要逻辑上移动0008及之后的所有记录,空出一个位置存储0007。
但是如果此时达到了页的上限,就需要分裂页,分裂页会导致性能下降。此外由于拆分了页导致原有的页利用率也会下降50%。如下图:
当然有分裂就有合并。当相邻两个页由于物理删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。
页合并是在相邻两个页的空间利用率低后采取的操作,会影响性能,因此实际业务中往往通过增加逻辑删除列来避免页合并,更重要的是,逻辑删除能避免丢失最重要的数据。
小结
这章主要分析了数据库引擎可用的数据结构,介绍了 InnoDB 采用的 B+ 树结构,以及为什么 InnoDB 要这么选择。
B+ 树能够很好地配合磁盘的读写特性,减少单次查询的磁盘访问次数。
此外由于 InnoDB 是索引组织表,一般情况下建议你创建一个自增主键,这样非主键索引占用的空间最小。此外还能够避免页分裂降低索引性能。