一颗B+树可以存储多少数据?

一、前言

这个问题,非常经典,考察的点很多:

比如:

        1、操作系统存储的单元,毕竟mysql也是运行在操作系统之上的应用。

        2、B+树是针对Mysql的InnoDB存储引擎,所以要理解InnoDb的最小存储单元,页,区,段的概念,尤其是页的格式,里面有哪些构成。

        2、明确这颗B+树一定是由主键索引构建的B+树,所以最终的数据是存储在叶子结点,非叶子节点,只存储主键索引,所以主键索引的大小决定了最终能存放多少数据。

答案:

主键为bigint(约2000w):

2层B+树的话:可以存放1170个*16条=18720条(行)数据。
3层B+树的话:可以存放1170个*1170个*16条=21902400条(行)数据。

主键为int(约4000w):

2层B+树的话:可以存放1600个*16条=25600条(行)数据。
3层B+树的话:可以存放1600个*1600个*16条=40960000条(行)数据。

在使用主流磁盘存储的条件一下,查询一条数据的时间在50ms内

二、磁盘存储和InnoDB的存储

我们都知道计算机在存储数据的时候,有最小存储单元,这就好比我们今天进行现金的流通最小单位是一毛。在计算机中磁盘存储数据最小单元是扇区,一个扇区的大小是512字节,而文件系统(例如XFS/EXT4)他的最小单元是块,一个块的大小是4k,

下面几张图可以帮你理解最小存储单元:

文件系统中一个文件大小只有1个字节,但不得不占磁盘上4KB的空间。

而对于我们的InnoDB存储引擎也有自己的最小储存单元——页(Page),一个页的大小是16K,如下图所示:

并且innodb所有的数据文件也就是我们之前提到的后缀为.ibd的文件,它们的大小都是16k的整数倍。

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

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

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

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

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

记录是按照行来存储的,但是数据库的读取并不以「行」为单位,否则一次读取(也就是一次 I/O 操作)只能处理一行数据,效率会非常低。

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

三、Innodb数据页格式

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

数据页包括七个部分,结构如下图:

这 7 个部分的作用如下图:

在 File Header 中有两个指针,分别指向上一个数据页和下一个数据页,连接起来的页相当于一个双向的链表,如下图所示:

采用链表的结构是让数据页之间不需要是物理上的连续的,而是逻辑上的连续。

数据页的主要作用是存储记录,也就是数据库的数据,所以重点说一下数据页中的 User Records 是怎么组织数据的。

数据页中的记录按照「主键」顺序组成单向链表,单向链表的特点就是插入、删除非常方便,但是检索效率不高,最差的情况下需要遍历链表上的所有节点才能完成检索。

因此,数据页中有一个页目录,起到记录的索引作用,就像我们书那样,针对书中内容的每个章节设立了一个目录,想看某个章节的时候,可以查看目录,快速找到对应的章节的页数,而数据页中的页目录就是为了能快速找到记录。

那 InnoDB 是如何给记录创建页目录的呢?页目录与记录的关系如下图:

页目录创建的过程如下:

  1. 将所有的记录划分成几个组,这些记录包括最小记录和最大记录,但不包括标记为“已删除”的记录;
  2. 每个记录组的最后一条记录就是组内最大的那条记录,并且最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段(上图中粉红色字段)
  3. 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录

从图可以看到,页目录就是由多个槽组成的,槽相当于分组记录的索引。然后,因为记录是按照「主键值」从小到大排序的,所以我们通过槽查找记录时,可以使用二分法快速定位要查询的记录在哪个槽(哪个记录分组),定位到槽后,再遍历槽内的所有记录,找到对应的记录,无需从最小记录开始遍历整个页中的记录链表。

以上面那张图举个例子,5 个槽的编号分别为 0,1,2,3,4,我想查找主键为 11 的用户记录:

  • 先二分得出槽中间位是 (0+4)/2=2 ,2号槽里最大的记录为 8。因为 11 > 8,所以需要从 2 号槽后继续搜索记录;
  • 再使用二分搜索出 2 号和 4 槽的中间位是 (2+4)/2= 3,3 号槽里最大的记录为 12。因为 11 < 12,所以主键为 11 的记录在 3 号槽里;
  • 这里有个问题,「槽对应的值都是这个组的主键最大的记录,如何找到组里最小的记录」?比如槽 3 对应最大主键是 12 的记录,那如何找到最小记录 9。解决办法是:通过槽 3 找到 槽 2 对应的记录,也就是主键为 8 的记录。主键为 8 的记录的下一条记录就是槽 3 当中主键最小的 9 记录,然后开始向下搜索 2 次,定位到主键为 11 的记录,取出该条记录的信息即为我们想要查找的内容。

看到第三步的时候,可能有的同学会疑问,如果某个槽内的记录很多,然后因为记录都是单向链表串起来的,那这样在槽内查找某个记录的时间复杂度不就是 O(n) 了吗?

这点不用担心,InnoDB 对每个分组中的记录条数都是有规定的,槽内的记录就只有几条:

  • 第一个分组中的记录只能有 1 条记录;
  • 最后一个分组中的记录条数范围只能在 1-8 条之间;
  • 剩下的分组中记录条数范围只能在 4-8 条之间。

四、B+ 树是如何进行查询的?

上面我们都是在说一个数据页中的记录检索,因为一个数据页中的记录是有限的,且主键值是有序的,所以通过对所有记录进行分组,然后将组号(槽号)存储到页目录,使其起到索引作用,通过二分查找的方法快速检索到记录在哪个分组,来降低检索的时间复杂度。

但是,当我们需要存储大量的记录时,就需要多个数据页,这时我们就需要考虑如何建立合适的索引,才能方便定位记录所在的页。

为了解决这个问题,InnoDB 采用了 B+ 树作为索引。磁盘的 I/O 操作次数对索引的使用效率至关重要,因此在构造索引的时候,我们更倾向于采用“矮胖”的 B+ 树数据结构,这样所需要进行的磁盘 I/O 次数更少,而且 B+ 树 更适合进行关键字的范围查询。

InnoDB 里的 B+ 树中的每个节点都是一个数据页,结构示意图如下:

通过上图,我们看出 B+ 树的特点:

  • 只有叶子节点(最底层的节点)才存放了数据,非叶子节点(其他上层节)仅用来存放目录项作为索引。
  • 非叶子节点分为不同层次,通过分层来降低每一层的搜索量;
  • 所有节点按照索引键大小排序,构成一个双向链表,便于范围查询;

我们再看看 B+ 树如何实现快速查找主键为 6 的记录,以上图为例子:

  • 从根节点开始,通过二分法快速定位到符合页内范围包含查询值的页,因为查询的主键值为 6,在[1, 7)范围之间,所以到页 30 中查找更详细的目录项;
  • 在非叶子节点(页30)中,继续通过二分法快速定位到符合页内范围包含查询值的页,主键值大于 5,所以就到叶子节点(页16)查找记录;
  • 接着,在叶子节点(页16)中,通过槽查找记录时,使用二分法快速定位要查询的记录在哪个槽(哪个记录分组),定位到槽后,再遍历槽内的所有记录,找到主键为 6 的记录。

可以看到,在定位记录所在哪一个页时,也是通过二分法快速定位到包含该记录的页。定位到该页后,又会在该页内进行二分法快速定位记录所在的分组(槽号),最后在分组内进行遍历查找。

总结

InnoDB 的数据是按「数据页」为单位来读写的,默认数据页大小为 16 KB。每个数据页之间通过双向链表的形式组织起来,物理上不连续,但是逻辑上连续。

数据页内包含用户记录,每个记录之间用单向链表的方式组织起来,为了加快在数据页内高效查询记录,设计了一个页目录,页目录存储各个槽(分组),且主键值是有序的,于是可以通过二分查找法的方式进行检索从而提高效率。

为了高效查询记录所在的数据页,InnoDB 采用 b+ 树作为索引,每个节点都是一个数据页。

如果叶子节点存储的是实际数据的就是聚簇索引,一个表只能有一个聚簇索引;如果叶子节点存储的不是实际数据,而是主键值则就是二级索引,一个表中可以有多个二级索引。

在使用二级索引进行查找数据时,如果查询的数据能在二级索引找到,那么就是「索引覆盖」操作,如果查询的数据不在二级索引里,就需要先在二级索引找到主键值,需要去聚簇索引中获得数据行,这个过程就叫作「回表」。

同理三层高的B+树

叶子节点的一条记录按照1k算

1170*1170*16 约等于 两千万

根结点常驻内存,根据磁盘每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。

那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右。

那么在有索引的条件下,查询一个数据大约需要两次IO,在两千万数据的量级下,时间可以控制在20ms左右。

参考文献

https://www.cnblogs.com/leefreeman/p/8315844.html

mysql 最大建议行数2000w,靠谱吗? - 京东云开发者的个人空间 - OSCHINA - 中文开源技术交流社区

 MySQL InnoDB引擎——三层B+树可以存储多少数据量_3层b+树可以存多少数据-CSDN博客

面试官问我为啥B+树一般都不超过3层?3层B+树能存多少数据?redo log与binlog的两阶段提交?_b+树为什么三层-CSDN博客

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要了解 5 阶 B+ 的结构。 一个 5 阶 B+ 的节点最多可以存储 4 个子节点和 3 个数据项。其中,除了根节点以外的其他节点必须至少存储 2 个数据项和 3 个子节点(如果不是叶子节点的话)。叶子节点存储数据项,非叶子节点存储的是键值。 现在,我们按照题目中给出的数据项依次插入到B+中,并且设置 L=3。插入过程如下: 1. 插入 11,形成以下: ``` [11] ``` 2. 插入 4,形成以下: ``` [11, 4] ``` 3. 插入 10,形成以下: ``` [4, 10, 11] ``` 4. 插入 7,形成以下: ``` [4, 7, 10, 11] ``` 5. 插入 20,形成以下: ``` [10] / \ [4, 7] [11, 20] ``` 6. 插入 19,形成以下: ``` [10] / \ [4, 7] [11, 19, 20] ``` 7. 插入 2,形成以下: ``` [10] / \ [2, 4, 7] [11, 19, 20] ``` 8. 插入 12,形成以下: ``` [10] / \ [2, 4, 7] [11, 12, 19, 20] ``` 9. 插入 13,形成以下: ``` [10, 19] / \ [2, 4, 7] [11, 12, 13] \ [19, 20] ``` 10. 插入 0,形成以下: ``` [10, 19] / \ [0, 2, 4] [7, 11, 12, 13] \ [19, 20] ``` 11. 插入 18,形成以下: ``` [10, 18] / \ [0, 2, 4] [7, 11, 12, 13] \ [18, 19, 20] ``` 12. 插入 17,形成以下: ``` [10, 18] / \ [0, 2, 4] [7, 11, 12, 13, 17] \ [18, 19, 20] ``` 13. 插入 15,形成以下: ``` [10, 18] / \ [0, 2, 4] [7, 11, 12, 13, 15, 17] \ [18, 19, 20] ``` 14. 插入 9,形成以下: ``` [7, 10, 18] / | \ [0, 2, 4] [7, 9] [11, 12, 13, 15, 17] [18, 19, 20] ``` 15. 插入 8,形成以下: ``` [7, 10, 18] / | | \ [0, 2, 4] [7, 8] [9] [11, 12, 13, 15, 17] [18, 19, 20] ``` 16. 插入 1,形成以下: ``` [7, 10, 18] / | | \ [0, 1, 2, 4] [7, 8] [9] [11, 12, 13, 15, 17] [18, 19, 20] ``` 17. 插入 16,形成以下: ``` [10] / \ [4, 7] [15, 18] / | \ / | \ [0, 1, 2] [5, 6] [7, 8, 9] [11, 12, 13, 16, 17] [19, 20] ``` 18. 插入 3,形成以下: ``` [7, 10] / | \ [1, 2, 3, 4] [7] [15, 18] / | \ / | \ [0] [1] [2] [5,6] [7,8,9] [11,12,13,16,17] [19,20] ``` 最终得到了一棵符合条件的 B+

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值