深入理解 MySQL 索引底层数据结构

文章探讨了MySQL索引的底层数据结构,包括Hash表、二叉树、红黑树、B-Tree和B+Tree,重点分析了B+Tree如何提高查询效率,并通过实例解释了千万级数据的快速查找。文章还提到了InnoDB存储引擎的索引实现,强调了主键重要性,特别是自增主键,并介绍了联合索引的工作原理。
摘要由CSDN通过智能技术生成

1 引言

在日常工作中,我们会遇见一些慢 SQL,在分析这些慢 SQL 时,我们通常会看下 SQL 的执行计划,验证 SQL 执行过程中有没有走索引。通常我们会调整一些查询条件,增加必要的索引,SQL 执行效率就会提升几个数量级。我们有没有思考过,为什么加了索引就会能提高 SQL 的查询效率,为什么有时候加了索引 SQL 执行反而会没有变化,本文就从 MySQL 索引的底层数据结构和算法来进行详细分析。

2 索引数据结构对比

索引的定义:索引 (Index) 是帮助 MySQL 高效获取数据的排好序的数据结构。

索引中常见的数据结构有以下几种:

  • Hash 表
  • 二叉树
  • 红黑树
  • B-Tree
  • B+Tree

Hash 表
通过索引的 key 进行一次 hash 计算,就可以快速获取磁盘文件指针,对于指定索引查找文件非常快,但是对于范围查找没法支持,有时候也会出现 Hash 冲突的情况。

二叉树
二叉树的特点:左边子节点的数据小于父节点数据,右边子节点的数据大于父节点数据。如下图所示,如果 col2 是索引,查找索引为 65 的行元素,只需要查找两次,就可以获取到行元素所在的磁盘指针地址。

但如果是一个按照顺序递增的值,例如为 col1 建立索引,不再适合使用二叉树建立索引,因为此时使用二叉树建立索引将会变成一个链式索引,此时的索引结构如下图所示,如果查找 6 节点需要 6 次遍历才能找到。

红黑树
红黑树是一种二叉平衡树,可以提高查询效率,此时若再查找 6 节点只需要遍历 3 次就能找到了。但红黑树也有缺点,当存储大数据量时,树的高度就会变的不可控, 数量越大,树的高度越高,查询的效率将会大大降低。

B-Tree
B-Tree 是一种多路二叉树,所具有的特点:1 叶节点具有相同的深度,叶节点的指针为空;2 所有索引元素不重复;3 节点中的数据索引从左到右递增排列。

B+Tree
B+Tree 是 B-Tree 的变种,所具有的特点:1 非叶子节点不存储 data,只存储索引 (冗余),可以放更多的索引;2 叶子节点包含所有索引字段;3 叶子节点用指针连接,提高区间访问的性能。

与红黑树相比,B-Tree 和 B+Tree 两种数据结构都更加矮胖,存储相同数量级的索引数据时,层级更低。

B-Tree 和 B+Tree 之间一个很大的不同,是 B+Tree 的节点上不储存 value,只储存 key,而叶子节点上储存了所有 key-value 集合,并且节点之间都是有序的。这样的好处是每一次磁盘 IO 能够读取的节点更多,也就是树的度 (Max.Degree) 可以设置的更大一些,因为每次磁盘 IO 读取的磁盘页数是一定的。例如,每次磁盘 IO 能够读取 1 页 = 4kb,那么省去 value 的情况下同样一页数据能够读取更多的 key,这样就大大减少了磁盘的 IO 次数。

此外,B+Tree 也是排好序的数据结构,数据库中 >< 或者 order by 等都可以直接依赖这一特性。

MySQL 中对于索引使用的主要数据结构也是 B+Tree,目的也是在读取数据时能够减少磁盘 IO。

3 千万级数据如何用 B + 树索引快速查找

MySQL 官方对非叶子节点 (如最上层 h = 1 的节点,B+Tree 高度为 3) 的大小是有限制的,最大的大小是 16K,可以通过以下 SQL 语句查询到,当然这个值是可以调的,既然官方给出这个阈值说明再大的话会影响磁盘 IO 效率。

从执行结果,可以看到大小为 16384,即 16K 大小。

假如:B+Tree 的表都存满了。主键索引的类型为 BigInt,大小为 8B,指针存储了下个节点的文件地址,大小为 6B。最后一层,假如 存放的数据 data 为 1K 大小,那么

  1. 第一层最大节点数为: 16k / (8B + 6B) ≈ 1170 (个);
  2. 第二层最大节点数也应为:1170 个;
  3. 第三层最大节点数为:16K / 1K = 16 (个)。

则,一张 B+Tree 的表最多存放 1170 1170 16 ≈ 2 千万。

所以,通过分析,我们可以得出,B+Tree 结构的表可以容纳千万数据量的查询。而且一般来说,MySQL 会把 B+Tree 根节点放在内存中,那只需要两次磁盘 IO 就行。

4 存储引擎索引实现

MySQL 中索引储存在哪里呢?和数据一样,索引以文件形式储存在硬盘上。
在 MyISAM 储存引擎中,数据和索引文件试试分开储存的,数据存在.MYD 结尾的文件中,索引单独存在.MYI 结尾的文件中。

在 InnoDB 中,数据和索引文件是合起来储存的,注意下图中没有了.MYI 结尾的文件,只有一个.ibd 结尾的文件。

MyISAM 索引文件和数据文件是分离的 (非聚集),并且主键索引和辅助索引(二级索引)的储存方式是一样的。

InnoDB 中索引文件和数据文件是同一个文件(聚集),并且主键索引和二级索引储存方式有所不同,如图所示,二级索引的叶子节点不储存数据,仅储存主键 ID。

这里思考几个问题:

  • 为什么建议 InnoDB 表必须建主键,并且推荐使用整型的自增主键?
  • 为什么非主键索引结构叶子节点存储的是主键值?

如果我们在创建表时不设置主键,InnoDB 会自动帮我们从第一列开始筛选一列数据不重复的列做为主键,如果找不到这样的列,就会创建一个隐藏的列(rowid)做为主键,这会增加很多 MySQL 的工作,所以建议我们在创建 InnoDB 表时一定要设置主键。

整型的字段做为主键,一方面在数据比较时不需要进行转换,另一方面存储也比较节省空间。那为什么要强调主键自增呢?如果主键 id 是无序的,那么很有可能新插入的值会导致当前节点分裂,此时 MySQL 不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过 OPTIMIZE TABLE 来重建表并优化填充页面。反之,如果每次插入有序,那就会在当前页后面连续写入,写不下就会重新分配一个节点,内存都是连续的,这样效率自然也就最高了。

非主键索引的叶子节点存储主键值而非全部数据,主要也是为了一致性和节省空间。如果二级索引储存的也是数据,那么每次插入 MySQL 都不得不更新每棵索引树,这样就加剧了新增编辑时的性能损耗,并且这样一来空间利用率也不高,必然产生了大量冗余数据。

5 联合索引底层数据结构又是怎样的

联合索引又叫复合索引,例如下表:

CREATE TABLE `test` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL,
`age` int NOT NULL,
`position` varchar(32) NOT NULL,
`address` varchar(128) NOT NULL,
`birthday` date NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

如下索引就是一个联合索引。

`idx_name_age_position` (`name`,`age`,`position`) USING BTREE

联合索引底层数据结构长什么样?

比较相等时,先比较第一列的值,如果相等,再继续比较第二列,以此类推。

了解了联合索引的存储结构,我们就知道了索引最左前缀优化原则是怎么回事了,在使用联合索引时,对于索引列的定义顺序将会影响到最终查询时索引的使用情况。例如联合索引(name,age,position),MySQL 会从最左边的列优先匹配,如果最左边的带头大哥 name 没有使用到,在未使用覆盖索引的情况下,就只能全表扫描。

联合底层数据结构思考:MySQL 会优先以联合索引第一列匹配,此后才会匹配下一列,如果不指定第一列匹配的值,也就无法得知下一步查询哪个节点。

6 总结

索引本质上是一种排好序的数据结构,了解了 MySQL 索引的底层数据结构及存储原理,可以帮助我们更好地进行 SQL 优化。其实数据库索引调优是一项技术活,不能仅仅靠理论,因为实际情况千变万化,而且 MySQL 本身存在很复杂的机制,如查询优化策略和各种引擎的实现差异等都会使情况变得更加复杂。但同时这些理论是索引调优的基础,只有在明白理论的基础上,才能对调优策略进行合理推断并了解其背后的机制,然后结合实践中不断的实验和摸索,从而真正达到高效使用 MySQL 索引的目的。

MySQL 索引底层数据结构主要有 B-Tree 和 Hash 结构两大类。 ### B-Tree B-Tree 是一种自平衡的树形数据结构,主要用于数据库和其他需要快速查找、插入和删除操作的数据存储系统中。它有以下几个关键特征: 1. **节点层次**:每个节点可以有多个子节点,并允许包含多个键值对,使得数据可以在树的较高层存储,提高查询效率。 2. **最大值限制**:节点中包含的最大键的数量是由节点的最大度数(分支因子)决定的。这意味着在同一级的节点之间存在某种形式的均匀分布。 3. **排序**:所有键都按升序排列,同时其左右子节点分别存储比当前节点小和大的键值部分。 4. **平衡**:通过调整内部结点的高度,保持整棵树的平衡状态,确保所有的路径长度大致相等。 ### Hash 结构 Hash 结构用于快速定位特定键对应的值。其核心在于利用哈希函数将键转换成一个哈希码,然后用这个哈希码作为索引来直接访问存储位置。 1. **哈希表**:基本的 hash 数据结构就是一个数组,每个元素对应着一个桶。当插入新元素时,使用哈希函数计算出该元素应该存放的位置,即哈希码对应的数组下标。 2. **冲突解决**:由于不同的键可能会得到相同的哈希码,因此需要策略处理这种冲突情况,常见的解决办法包括线性探测、链地址法和二次探查等。 3. **动态调整**:为了维持性能,哈希表通常会通过调整大小或重新哈希函数等方式来应对负载增加的情况。 ### MySQL 中的索引应用 MySQL 使用 B-Tree 结构来构建其默认类型的索引(如BTREE),这使得索引具有高效搜索、插入和删除的特点。对于 Hash 索引,则在某些场景下提供更快的查找速度,尤其是在单个列上使用并且数据集不是非常庞大时。 了解索引底层数据结构有助于优化查询性能,合理设计数据库结构和查询语句,以及更好地理解和管理数据库的运行状况。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员万万

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值