# 第 7 部分 - B 树简介

> 原文： [https://cstack.github.io/db_tutorial/parts/part7.html](https://cstack.github.io/db_tutorial/parts/part7.html)

B-Tree 是 SQLite 用来表示表和索引的数据结构，因此它是一个非常重要的想法。本文将介绍数据结构，因此它不会有任何代码。

为什么树是数据库的良好数据结构？

*   搜索特定值很快（对数时间）
*   插入/删除你已经找到的值很快（重新平衡的时间是恒定的）
*   遍历一系列值很快（与哈希映射不同）

B 树与二叉树不同（“B”可能代表发明人的名字，但也可以代表“平衡”）。这是 B-Tree 的一个例子：

![example B-Tree (https://en.wikipedia.org/wiki/File:B-tree.svg)](img/f4ef578e6c7aa322690f099ff7a3e500.jpg)

示例 B-Tree（https://en.wikipedia.org/wiki/File:B-tree.svg）

与二叉树不同，B 树中的每个节点可以有两个以上的子节点。每个节点最多可以有 m 个子节点，其中 m 称为树的“阶数”。为了保持树大部分平衡，我们还说节点必须至少有`m / 2`个子节点（四舍五入）。

例外：

*   叶子节点有 0 个子节点
*   根节点可以有少于 m 个子节点但必须至少有 2 个
*   如果根节点是叶子节点（唯一节点），则它仍然有 0 个子节点

上面的图片是一个 B-Tree，SQLite 用它来存储索引。为了存储表，SQLites 使用称为 B+ 树的变体。

|  | B 树 | B +树 |
| --- | --- | --- |
| 发音 | “Bi4 树” | “Bi4 加树” |
| 用来存放 | 索引 | 表 |
| 内部节点存储键 | 是 | Yes |
| 内部节点存储值 | Yes | 没有 |
| 每个节点的子节点数 | 少 | 多 |
| 内部节点和叶子节点 | 结构相同 | 结构不同 |

在我们开始实现索引之前，我将仅讨论 B+ 树，但我只是将其称为 B 树或 b 树。

带子节点的节点称为“内部”节点。内部节点和叶子节点的结构不同：

| 对于 m 阶树 | 内部节点 | 叶子节点 |
| --- | --- | --- |
| 储存 | 键和子节点的指针 | 键和值 |
| 键数 | 最多 m-1 | 尽可能多 |
| 指针数量 | 键数 + 1 | 没有 |
| 值的数量 | 没有 | 键数 |
| 主要目的 | 用于路由 | 和值配对 |
| 存储价值？ | 否 | 是 |

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

*   每个内部节点最多有 3 个子节点
*   每个内部节点最多有 2 个键
*   每个内部节点至少有 2 个子节点
*   每个内部节点至少有一个键

空 B 树具有单个节点：根节点。根节点作为具有零键/值对的叶子节点开始：

![empty btree](img/604730ce3bf068dd6cc1dd78db64926f.jpg)

空的 btree

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

![one-node btree](img/5045c4462e09e087b99196ab3e608525.jpg)

单节点 btree

假设叶子节点的容量是两个键/值对。当我们插入另一个时，我们必须拆分叶子节点并在每个节点中放置一半对。两个节点都成为新内部节点的子节点，现在它将成为根节点。

![two-level btree](img/9b1d283f73bd4b427dee7e63ddf2f106.jpg)

两级 btree

内部节点有 1 个键和 2 个指向子节点的指针。如果我们想要查找小于或等于 5 的键，我们会查看左边的子节点。如果我们想要查找大于 5 的键，我们会找到右边的子节点。

现在让我们插入键“2”。首先，如果它存在，我们将查找它将在哪个叶子节点，并且我们到达左叶子节点。节点已满，因此我们拆分叶子节点并在父节点中创建新条目。

![four-node btree](img/808fcc4cb81d3b787e9d592fd39af3a2.jpg)

四节点 btree

让我们继续添加键。我们到了必须再次拆分的地步，但父节点中没有空间用于另一个键/指针对。

![no room in internal node](img/cbc61b11cdcb7cf0e0783bfc10db4bcd.jpg)

内部节点没有空间

解决方案是将根节点拆分为两个内部节点，然后创建新的根节点作为其父节点。

![three-level btree](img/eea28b7eb6f1a4e6b88a19cac22373d5.jpg)

三级 btree

当我们拆分根节点时，树的深度才会增加。每个叶子节点具有相同的深度并且接近相同数量的键/值对，因此树保持平衡并且搜索快速。

在我们实现插入之后，我将推迟讨论从树中删除键的问题。

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

下一次，我们开始实现 btree！