2020-08-16

数据库为什么使用B+树而不是B树

  • B树只适合随机检索,而B+树同时支持随机检索和顺序检索;
  • B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;
  • B+树的查询效率更加稳定。B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。
  • B-树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作。
  • 增删文件(节点)时,效率更高。因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。

链表

双向链表支持顺序查找和逆序查找,如图下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jFju5U3z-1597544357047)(https://pics2.baidu.com/feed/f9198618367adab4367eaa2f1f50c91a8601e4bf.png?token=f8ff364851f171376d701af2790f9bf7)]

跳表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHcdiwfq-1597544357053)(https://pics5.baidu.com/feed/78310a55b319ebc425e5184f17a2b5fa1c1716d5.png?token=12a2f7bebc58ef613de82030c15de047)]

假设我们现在要查找区间 7- 13 的记录,再也不用从头开始查找了,只要在上图中的二级索引开始找即可,遍历三次即可找到链表的区间位置,时间复杂度是 O(logn),非常快,这样看来,跳表是能满足我们的需求的,实际上它的结构已经和 B+ 树非常接近了,只不过 B+ 树是从平衡二叉查找树演化而来的而已,接下来我们一步步来看下如何将平衡二叉查找树改造成 B+ 树。

平衡二叉查找树

若左子树不空,则左子树上所有节点的值均小于它的根节点的值;若右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;每个非叶子节点的左右子树的高度之差的绝对值(平衡因子)最多为1。下图就是一颗平衡二叉查找树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xn4YXfxN-1597544357065)(https://pics1.baidu.com/feed/08f790529822720eae925a07ed4f7040f31fab7e.jpeg?token=e3e1c04ec57b899a87b2880d59781741)]

从其特性就可以看到平衡二叉查找树查找节点的时间复杂度是 O(log2n)

改造成 B+ 树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uJ8qyKL6-1597544357069)(https://pics7.baidu.com/feed/d439b6003af33a872c65bc7d53d86a3e5243b5dc.png?token=bd673f17eda50f168a64898128f4df93)]

假设有 1 亿个节点,每个节点要查询多少次呢,显然最多为 log21亿 = 27 次,如果这 1 亿个节点都在内存里,那 27 次显然不是问题,可以说是非常快了,但一个新的问题出现了,这 1 亿个节点在内存大小是多少呢,我们简单算一下,假设每个节点 16 byte,则 1 亿个节点大概要占用 1.5G 内存!对于内存这么宝贵的资源来说是非常可怕的空间消耗,这还只是一个索引,一般我们都会在表中定义多个索引,或者库中定义多张表,这样的话内存很快就爆满了!所以在内存中完全装载一个 B+ 树索引显然是有问题的,如何解决呢。

内存放不下, 我们可以把它放到磁盘嘛,磁盘空间比内存大多了,但新的问题又来了,我们知道内存与磁盘的读取速度相差太大了,通常内存是纳秒级的,而磁盘是毫秒级的,读取同样大小的数据,两者可能相差上万倍,于是上一步我们计算的 27 次查询如果放在磁盘中来看就非常要命了(查找一个节点可以认为是一次磁盘 IO,也就是说有 27 次磁盘 IO!),27 次查询是否可以优化?

可以很明显地观察到查询次数和树高有关,那树高和什么有关,很明显和每个节点的子节点个数有关,即 N 叉树中的 N,假设现在有 16 个数,我们分别用二叉树和五叉树来构建,看下树高分别是多少

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJyvP49v-1597544357072)(https://pics2.baidu.com/feed/42166d224f4a20a42221cf6a06d6e224730ed0ef.png?token=e246493ff62fd4e09cdba94993f08aaa)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BD3pP0gj-1597544357074)(https://pics6.baidu.com/feed/55e736d12f2eb9381b75bdac42e6ff33e4dd6fdb.png?token=d946d81ee04d9dca020a4e385ba38977)]

可以看到如果用二叉树 ,要遍历 5 个节点,如果用五叉树 ,只要遍历 3 次,一下少了两次磁盘 IO,回过头来看 上文的一亿个节点,如果我们用 100 叉树来构建,需要几次 IO 呢

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uY71zCDB-1597544357077)(https://pics0.baidu.com/feed/e850352ac65c1038bcbda47c2b95e915b27e89dc.png?token=08761b78f5a45254c0966f71752ff362)]

可以看到,最多遍历五次(实际上根节点一般存在内存里的,所以可以认为是 4 次)!磁盘 IO 一下从 27 减少到了 5!性能可以说是大大提升了,有人说 5 次还是太多,是不是可以把 100 叉树改成 1000 或 10000 叉树呢,这样 IO 次数不就就能进一步减少了。

这里我们就需要了解页(page)的概念,在计算机里,无论是内存还是磁盘,操作系统都是按页的大小进行读取的(页大小通常为 4 kb),磁盘每次读取都会预读,会提前将连续的数据读入内存中,这样就避免了多次 IO,这就是计算机中有名的局部性原理,即我用到一块数据,很大可能这块数据附近的数据也会被用到,干脆一起加载,省得多次 IO 拖慢速度, 这个连续数据有多大呢,必须是操作系统页大小的整数倍,这个连续数据就是 MySQL 的页,默认值为 16 KB,也就是说对于 B+ 树的节点,最好设置成页的大小(16 KB),这样一个 B+ 树上的节点就只会有一次 IO 读。

那有人就会问了,这个页大小是不是越大越好呢,设置大一点,节点可容纳的数据就越多,树高越小,IO 不就越小了吗,这里要注意,页大小并不是越大越好,InnoDB 是通过内存中的缓存池(pool buffer)来管理从磁盘中读取的页数据的。页太大的话,很快就把这个缓存池撑满了,可能会造成页在内存与磁盘间频繁换入换出,影响性能。

通过以上分析, N 选的时候尽量保证每个节点的大小等于一个页(16kb)的大小即可。

InnoDB 引擎中数据的存储方式

聚集索引就是按照每张表的主键构造一颗 B+ 树,它的叶子节点存放的是整行数据。

InnoDB 的主键一定是聚集索引。如果没有定义主键,聚集索引可能是第一个不允许为 null 的唯一索引,也有可能是 row id。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值