MySQL 为什么采用 B+ 树作为索引?

文章讨论了MySQL中如何通过B+树优化数据存储和索引设计,减少磁盘I/O,以提高查询性能,重点介绍了二分查找树、AVL树和B树的优缺点,以及InnoDB的B+树特性,如数据页、聚簇索引和非聚簇索引的区别。
摘要由CSDN通过智能技术生成

MySQL 的数据是持久化的,数据(索引+记录)是保存到磁盘上

磁盘的读写比起内存来说要慢上数万倍,毕竟内存的读写是纳秒级别的,而磁盘是毫秒级别的

对于磁盘来说,它的读写的最小单位是扇区,扇区的大小只有 512B 大小。

操作系统的最小读写单位是块(Block),Linux 中的块大小为 4KB,也就是说操作系统从磁盘中一次最小的读写,这个磁盘 I/O 操作会直接读写 8 个扇区(4KB/512B)。

InnoDB 的数据是按「数据页」为单位来读写的,也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。

数据库的 I/O 操作的最小单位是页,InnoDB 数据页的默认大小是 16KB,意味着数据库每次读写都是以 16KB 为单位的,一次最少从磁盘中读取 16K 的内容到内存中,一次最少把内存中的 16K 内容刷新到磁盘中。

InnoDB会将其数据页的大小调整为与操作系统块大小相匹配,或者至少是其整数倍,以优化I/O性能。

由于数据库的索引是保存到磁盘上的,我们通过索引查找某个记录时,就需要先从磁盘读取索引到内存,再通过索引从磁盘中找到记录,然后读入到内存,也就是说查询过程中会发生多次磁盘 I/O,而磁盘 I/O 次数越多,所消耗的时间也就越大。

所以,我们希望索引的数据结构能在尽可能少的磁盘的 I/O 操作中完成查询工作,因为磁盘 I/O 操作越少,所消耗的时间也就越小。

另外,MySQL 是支持范围查找的,所以索引的数据结构不仅要能高效地查询某一个记录,而且也要能高效地执行范围查找。

所以,要设计一个适合 MySQL 索引的数据结构,至少满足以下要求:

  • 能在尽可能少的磁盘的 I/O 操作中完成查询工作;
  • 要能高效地查询某一个记录,也要能高效地执行范围查找;

分析完要求后,针对每一个数据结构进行分析

二分查找树

索引数据最好能按顺序排列,这样可以使用「二分查找法」高效定位数据。所以自然是想到树。

二叉查找树的特点是一个节点的左子树的所有节点都小于这个节点,右子树的所有节点都大于这个节点,这样我们在查询数据时,不需要计算中间节点的位置了,只需将查找的数据与节点的数据进行比较。

我们查找索引值为 key 的节点:

  1. 如果 key 大于根节点,则在右子树中进行查找;
  2. 如果 key 小于根节点,则在左子树中进行查找;
  3. 如果 key 等于根节点,也就是找到了这个节点,返回根节点即可。

那是不是二叉查找树就可以作为索引的数据结构了呢?

NO,二叉查找树存在一个极端情况,会导致它变成一个瘸子!当每次插入的元素都是二叉查找树中最大的元素,二叉查找树就会退化成了一条链表,查找数据的时间复杂度变成了 O(n)

树是存储在磁盘中的,访问每个节点,都对应一次磁盘 I/O 操作(假设一个节点的大小「小于」操作系统的最小读写单位块的大小),也就是说树的高度就等于每次查询数据时磁盘 IO 操作的次数,所以树的高度越高,就会影响查询性能。

二叉查找树由于存在退化成链表的可能性,会使得查询操作的时间复杂度从 O(logn) 升为 O(n)。

而且会随着插入的元素越多,树的高度也变高,意味着需要磁盘 IO 操作的次数就越多,这样导致查询性能严重下降,再加上不能范围查询,所以不适合作为数据库的索引结构。

自平衡二叉树

为了解决二叉查找树会在极端情况下退化成链表的问题,后面提出平衡二叉查找树(AVL 树)

每个节点的左子树和右子树的高度差不能超过 1 ,这样查询操作的时间复杂度就会一直维持在 O(logn) 。

我们常说的红黑树也是自平衡的二叉树,也是通过一些约束条件来达到平衡。

不管平衡二叉查找树还是红黑树,都会随着插入的元素增多,而导致树的高度变高,这就意味着磁盘 I/O 操作次数多,会影响整体数据查询的效率

如下一颗AVL树,我们要查找L5的数据,那岂不是要进行5次I/O。

 B 树

针对AVL树的问题,我们把二叉树改成 M 叉树,比如 M=3 时,在同样的节点个数情况下,三叉树比二叉树的树高要矮。

为了解决降低树的高度的问题,就有了 B 树,它不再限制一个节点就只能有 2 个子节点,而是允许 M 个子节点 (M>2),从而降低树的高度。

同样的节点数量在平衡二叉树的场景下,树的高度就会很高,意味着磁盘 I/O 操作会更多。所以,B 树在数据查询中比平衡二叉树效率要高。

但是 B 树的每个节点都包含数据(索引+记录),而用户的记录数据的大小很有可能远远超过了索引数据,这就需要花费更多的磁盘 I/O 操作次数来读到「有用的索引数据」。

而且,在我们查询位于底层的某个节点(比如 A 记录)过程中,「非 A 记录节点」里的记录数据会从磁盘加载到内存,但是这些记录数据是没用的,我们只是想读取这些节点的索引数据来做比较查询,而「非 A 记录节点」里的记录数据对我们是没用的,这样不仅增多磁盘 I/O 操作次数,也占用内存资源。

另外,如果使用 B 树来做范围查询的话,需要使用中序遍历,这会涉及多个节点的磁盘 I/O 问题,从而导致整体速度下降。

B+ 树

B+ 树就是对 B 树做了一个升级

B+ 树与 B 树差异的点,主要是以下这几点:

  • 叶子节点(最底部的节点)才会存放实际数据(索引+记录),非叶子节点只会存放索引;
  • 所有索引都会在叶子节点出现,叶子节点之间构成一个有序链表;
  • 非叶子节点的索引也会同时存在在子节点中,并且是在子节点中所有索引的最大(或最小)。
  • 非叶子节点中有多少个子节点,就有多少个索引;

B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少

B+ 树的插入和删除效率更高

B+ 树所有叶子节点间还有一个链表进行连接,这种设计对范围查找非常有帮助

MySQL 中的 B+ 树

INNODB中的B+树:

 Innodb 使用的 B+ 树有一些特别的点,比如:

  • B+ 树的叶子节点之间是用「双向链表」进行连接,这样的好处是既能向右遍历,也能向左遍历。
  • B+ 树点节点内容是数据页,数据页里存放了用户的记录以及各种信息,每个数据页默认大小是 16 KB。

Innodb 根据索引类型不同,分为聚簇索引和非聚簇索引(辅助索引,二级索引)。他们区别在于,聚簇索引的叶子节点存放的是实际数据,所有完整的用户记录都存放在聚簇索引的叶子节点,而二级索引的叶子节点存放的是主键值,而不是实际数据,需要通过回表才能查到实际数据。

因为表的数据都是存放在聚簇索引的叶子节点里,所以 InnoDB 存储引擎一定会为表创建一个聚簇索引,且由于数据在物理上只会保存一份,所以聚簇索引只能有一个,而二级索引可以创建多个。

借鉴转载:为什么 MySQL 采用 B+ 树作为索引? | 小林coding

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值