二叉搜索树,B树,B+树,索引

mongodb的默认存储引擎WiredTiger使用了B树索引

mysql的默认存储引擎InnoDB索引使用了B+树实现,那么各自为什么这样实现呢?

二叉搜索树

如上图是一个简单的二叉搜索树,是最为大家熟知的一种数据结构,它为什么不适合用作数据库索引?

(1)当数据量大的时候,树的高度会比较高,数据量大的时候,查询会比较慢

(2)每个节点只存储一个记录,可能导致一次查询有很多次磁盘IO

B树

B树的特点是:

(1)不再是单纯的二叉而是m叉

(2)叶子节点,非叶子节点,都存储数据

(3)中序遍历,可以获得所有节点

(4)非根节点包含的关键字个数j满足,(┌m/2┐)-1 <= j <= m-1,节点分裂时要满足这个条件。

B+树

B+树,如上图,仍是m叉搜索树,在B树的基础上,做了一些改进:

(1)非叶子节点不再存储数据,数据只存储在同一层的叶子节点上;B+树种 根到每一个节点的路径长度一样,而B树不是这样

(2)叶子之间,增加了链表,获取所有节点,不再需要中序遍历

这些改进让B+树比B树有更优的特性:

(1)范围查找,定位min与max之后,中间叶子节点,就是结果集,不用中序回溯;范围查询在SQL中用得多,这是B+树比B树最大的优势。

(2)叶子节点存储实际记录行,记录行相对比较紧密的存储,适合大数据量磁盘存储;非叶子节点存储记录的PK,用于查询加锁,适合内存存储。

(3)非叶子节点,不存储实际记录,而只存储记录的key的话,那么在相同内存的情况下,B+树能存储更多的索引。

B树作为实现索引的数据结构被创作出来,是因为它能够完美的利用局部性原理

什么是局部性原理?

内存读写块,磁盘读写慢,而且慢很多

磁盘预读:磁盘读写并不是按需读取,而是按页读取,一次会读一页的数据,每次加载更多的数据,如果未来要读取的数据就在这一页(一般是4K)中,可以避免未来的磁盘IO,提高效率

软件设计要尽量遵循数据读取集中与使用到一个数据,大概率会使用其附近的数据,这样磁盘预读能充分提高磁盘IO

B树为什么适合做索引?

(1)由于是m叉的,高度能大大降低;

(2)每个节点可以存储j个记录,如果将节点大小设置为页大小,例如4K,能够充分的利用预读的特性,极大减少磁盘IO

为什么mysql选用B+树而不是B树?而mongodb选用B树

先看一下B树和B+树是如何通过索引找到数据的

1.单条数据的查询

从上图B树和B+树的特点可以了解到,B树的单条数据查询所需要的平均随机IO次数会比B+树要少,因为B+的非叶子节点并不存储数据。而B树存储数据

2.范围查询

假设我们需要访问所有『大于 4,并且小于 9 的数据』

如果不考虑任何优化,在上面的简单B树中我们需要定位到4所在的位置,然后中序遍历,直到找到>=9的数据,中间有

如果是B+树,定位到<=4的位置,然后遍历链表即可

总结:mongodb对于单条数据的查找可能会更多,而mysql的范围查询可能更有优势

所以这可能与它们的设计有关

作为非关系型的数据库,MongoDB 对于遍历数据的需求没有关系型数据库那么强,它追求的是读写单个记录的性能。

而mysql可能更追求范围查询

SELECT * FROM comments WHERE created_at > '2019-01-01'

很多人看到遍历数据的查询想到的可能都是如上所示的范围查询,然而在关系型数据库中更常见的其实是如下所示的 SQL —— 查询外键或者某字段等于某一个值的全部记录:

SELECT * FROM comments WHERE post_id = 1

这里假如post_id=1的数据有很多,其实也是变相的范围查询,如果comments表上有索引post_id,那么这个查询可能就会在索引中遍历相应索引,找到满足条件的comment,这种查询也会受益于mysql B+树相互连接的叶节点,因为它能减少磁盘IO次数

mongodb作为非关系型的数据库,它从集合的设计上就使用了完全不同的方法,如果我们仍然使用传统的关系型数据库的表设计思路来思考mongodb中集合的设计,写出类似如上所示的查询会带来比较差的性能:

db.comments.find( { post_id: 1 } )

因为B树的所有节点都能存储数据,各个连续的节点之间没有很好的办法通过指针相连,所以上述查询在B树中性能会比在B+树中差很多,但是这并不是mongodb中推荐的设计方法,更合适的做法其实是使用内嵌文档,将post和属于它的所有comments都存储到一起

{    "_id": "...",    "title": "为什么 MongoDB 使用 B 树",    "author": "draven",    "comments": [        {            "_id": "...",            "content": "你这写的不行"        },        {            "_id": "...",            "content": "一楼说的对"        }    ]}

使用上述方式对数据进行存储时就不会遇到 db.comments.find( { post_id: 1 } ) 这样的查询了,我们只需要将 post 取出来就会获得相关的全部评论,这种区别于传统关系型数据库的设计方式是需要所有使用 MongoDB 的开发者重新思考的,这也是很多人使用 MongoDB 后却发现性能不如 MySQL 的最大原因 —— 使用的姿势不对。

那么mongodb既然认为查询单个数据记录远比遍历数据的查询更加常见,为什么不使用hash作为底层的数据结构呢?

如果我们使用哈希,那么对于所有单条记录查询的复杂度都会是O(1),但是遍历数据的复杂度就是O(n)

如果使用B+树,单条记录的查询的复杂度是O(logN),遍历数据的复杂度就是O(logN)+X

这两种不同的数据结构一种提供了最好的单记录查询性能,一种提供了最好的遍历数据的性能,但是这都不能满足mongodb面对的常见——单记录查询非常常见,但是对于遍历数据也需要有相对较好的性能支持。

参考文章:

https://mp.weixin.qq.com/s?__biz=MzU5NTAzNjc3Mg==&mid=2247484117&idx=1&sn=9162d427efff678c9741fb9583518287&chksm=fe795ddec90ed4c894041d4b535534d6a39f4608907689de13034534014b4573dd99b3ac534f&mpshare=1&scene=1&srcid=&sharer_sharetime=1578825193967&sharer_shareid=9ec7bdcffdff475d54d1e981f82a613d&rd2werd=1#wechat_redirect

https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651962936&idx=1&sn=2f4a97187134ed584273550104672694&chksm=bd2d0be48a5a82f2e5703e55272f6e3ee60954efd8d08b46232c3e24ec5632691ccc66973553&scene=21#wechat_redirect

发布了47 篇原创文章 · 获赞 39 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_28119741/article/details/103951357