Part 07 - B 型树简介

B 树是 SQLite 用来表示表和索引的数据结构, 所以它是一个相当核心的概念。 这篇文章将只是介绍这个数据结构, 所以不会有任何代码。

为什么说树是数据库的一个好的数据结构?

  • 搜索一个特定的值是快速的 (对数时间)。

  • 插入 / 删除一个你已经找到的值是快速的 (重新平衡的时间是恒定的)。

  • 遍历一个值的范围是快速的 (不像哈希图)。

B 树不同于二进制树 ("B"可能代表发明者的名字, 但也可能代表 "平衡")。 下面是一个 B 树的例子:

../../_images/B-tree.svg

example B-Tree (https://en.wikipedia.org/wiki/File:B-tree.svg)

与二叉树不同, B 树中的每个节点可以有 2 个以上的子节点。 每个节点最多可以有 m 个子节点, 其中 m 被称为树的 "顺序"。 为了保持树的基本平衡, 我们还说节点必须至少有 m/2 个子节点 (四舍五入)。

异常情况:

  • 叶子结点有 0 个孩子

  • 根节点可以有少于 m 个子节点, 但必须至少有 2 个子节点

  • 如果根节点是一个叶子节点 (唯一的节点), 它仍然有 0 个子节点

上面的图片是一个 B 树, SQLite 用它来存储索引。 为了存储表, SQLite 使用了一种叫做 B+ 树的变体。

Rows

B-tree

B+tree

Pronounced

"Bee Tree"

"Bee Plus Tree"

Used to store

Indexes

Tables

Internal nodes store keys

Yes

Yes

Internal nodes store values

Yes

No

Number of children per node

Less

More

Internal nodes vs. leaf nodes

Same structure

Different structure

在我们实现索引之前, 我只谈 B+ 树, 但我只把它称为 B 树或 btree。

有子节点的节点被称为 "内部" 节点。 内部节点和叶子结点的结构是不同的。

For an order-m tree...

Internal Node

Leaf Node

Stores

keys and pointers to children

keys and values

Number of keys

up to m-1

as many as will fit

Number of pointers

number of keys + 1

none

Number of values

none

number of keys

Key purpose

used for routing

paired with value

Stores values?

No

Yes

让我们通过一个例子来看看当你插入元素时, B 树是如何增长的。 为了简单起见, 这棵树将是 3 阶的。 这意味着:

  • 每个内部节点最多有 3 个子节点

  • 每个内部节点最多两个键

  • 每个内部节点至少有 2 个子节点

  • 每个内部节点至少有 1 个键

一个空的 B 树只有一个节点: 根节点。 根节点开始时是一个叶子节点, 有零个键 / 值对。

../../_images/btree1.png

empty btree

如果我们插入几个键 / 值对, 它们会按排序顺序存储在叶子节点中。

../../_images/btree2.png

one-node btree

比方说一个叶子节点的容量是两个键 / 值对。 当我们插入另一个节点时, 我们必须拆分叶子节点, 把一半的键值对放在每个节点中。 这两个节点都成为一个新的内部节点的子节点, 这个内部节点现在将是根节点。

../../_images/btree3.png

two-level btree

内部节点有 1 个键和 2 个指向子节点的指针。 如果我们想查找一个小于或等于 5 的键, 我们在左边的子节点中查找。 如果我们想查找一个大于 5 的键, 我们就在右边的子节点中查找。

现在让我们插入键 "2"。 首先, 我们查找它在哪个叶子节点中, 如果它是存在的, 我们到达左边的叶子节点。 这个节点已经满了, 所以我们把叶子节点拆开, 在父节点中创建一个新条目。

../../_images/btree4.png

four-node btree

让我们继续添加 Key: 18 和 21。 我们到了必须再次分割的地步, 但在父节点中没有空间容纳另一个键 / 指针对。

../../_images/btree5.png

no room in internal node

解决办法是将根节点分成两个内部节点, 然后创建新的根节点作为它们的父节点。

../../_images/btree6.png

three-level btree

只有当我们分割根节点时, 树的深度才会增加。 每个叶子节点都有相同的深度和接近相同数量的键 / 值对, 所以树保持平衡和快速搜索。

在我们实现插入之前, 我将暂不讨论从树上删除键的问题。

当我们实现这个数据结构时, 每个节点将对应于一个页面。 根节点将存在于第 0 页。 子节点的指针将只是包含子节点的页号。

下一节, 我们开始实现 btree!