MySQL InnoDB 使用 B+ 树(B+Tree) 作为其主要的索引结构,用于 主键索引(聚簇索引) 和 辅助索引(二级索引)。B+ 树相比 AVL 树、红黑树等数据结构,更适合数据库的 大规模数据存储 和 磁盘存取优化。
B+ 树是一种 平衡树,它具有以下特点:
以 3 阶 B+ 树为例(每个节点最多有 3 个子节点):
[10, 20]
/ | \
[1, 5] [10,15] [20, 30]
[1,5]
、[10,15]
、[20,30]
存储数据,并且 形成链表,便于范围查询。[10,20]
仅存索引(不存储数据)。15
,先比对 [10, 20]
,确定 15
在 10-20
之间,再进入 [10,15]
叶子节点,查找到数据。数据库索引需要 高效查找 和 范围查询,B+ 树相比 AVL 树、红黑树等具有明显优势。
特点 | AVL 树 | B+ 树 |
---|---|---|
树的高度 | 高,最多 2 个子节点 | 低,每个节点多个子节点 |
查找性能 | (O(log n)) | (O(log_m n))(更快) |
磁盘 I/O | 频繁访问磁盘 | 适合磁盘存储,减少 I/O |
范围查询 | 需要中序遍历 | 叶子节点链表结构,更快 |
索引大小 | 占用空间大 | 占用空间小,适合数据库 |
数据库索引的数据量通常大于内存,导致查询需要磁盘访问。
B+ 树优化磁盘 I/O 主要有两点:
B+ 树的叶子节点按顺序排列,并用双向链表连接,可以高效执行:
如果使用 AVL 树或红黑树,范围查询需要中序遍历整个树,效率较低。
MySQL InnoDB 使用 B+ 树 作为索引存储结构:
MySQL InnoDB B+ 树的核心代码在 storage/innobase/include/btr0btr.ic
和 btr0btr.cc
中。
void btr_cur_insert(
rec_t* insert_rec, // 要插入的记录
page_t* page, // B+树页
ulint* page_offset) {
// 1. 确定插入位置
if (is_leaf(page)) {
// 2. 插入到叶子节点
page_insert(insert_rec, page, page_offset);
} else {
// 3. 递归查找下层节点
btr_cur_insert(insert_rec, get_next_page(page), page_offset);
}
}
rec_t* btr_cur_search(
const key_t* key,
page_t* page) {
if (is_leaf(page)) {
// 在叶子节点查找
return page_search_leaf(key, page);
} else {
// 继续向下查找
return btr_cur_search(key, get_next_page(page));
}
}
如果 MySQL InnoDB 不使用 B+ 树,而使用其他数据结构,会导致:
MySQL 采用 B+ 树,减少磁盘 I/O,提高查询效率,并支持高效的范围查询,是最优选择。
B+ 树是 MySQL InnoDB 的核心索引结构,是数据库高效运行的关键!
B+ 树不仅在数据库索引中广泛使用,还在许多需要高效数据存储、查询和范围查找的场景中应用。以下是 B+ 树的几个典型应用场景:
BETWEEN
、ORDER BY
)。B+ 树在文件系统中用于管理磁盘上的索引和加速文件查找。
搜索引擎需要高效的数据结构来索引网页或文档,B+ 树在以下方面有重要应用:
B+ 树用于键值存储(KV Store),提高查询效率。
场景 | 应用案例 |
---|---|
数据库 | MySQL、PostgreSQL、MongoDB、Oracle |
文件系统 | Ext4、NTFS、XFS、Btrfs |
操作系统 | 内存管理、分页存储 |
搜索引擎 | Elasticsearch、Lucene、倒排索引 |
KV 存储 | LevelDB、RocksDB、Redis AOF |
网络 | IP 路由表、CDN 缓存 |
游戏 | AI 索引、路径规划 |
区块链 | Merkle B+ 树索引 |
B+ 树的核心优势:
B+ 树是数据库、文件系统、搜索引擎和网络应用的核心数据结构,为高效数据存储和检索提供了强大支持!
可以使用以下方式:
如果你的项目基于 Java、Python 或 C++,可以直接使用成熟的 B+ 树库,而不必自己从零实现。
Apache Commons Collections
(BTree
实现的是 B+ 树)H2 Database
的 MVStore
(支持 B+ 树索引)import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
public class BPlusTreeExample {
public static void main(String[] args) {
// 创建一个持久化存储的 B+ 树
MVStore store = MVStore.open("data.mv");
MVMap<Integer, String> bPlusTree = store.openMap("index");
// 插入数据
bPlusTree.put(1, "Alice");
bPlusTree.put(2, "Bob");
bPlusTree.put(3, "Charlie");
// 查询数据
System.out.println("ID=2 对应的值:" + bPlusTree.get(2));
// 关闭存储
store.close();
}
}
✅ 持久化存储
✅ 支持范围查询
✅ 性能高,代码简单
使用 bplustree
库:
from bplustree import BPlusTree
# 创建 B+ 树(持久化存储)
tree = BPlusTree('index.db', order=4)
# 插入数据
tree[1] = "Alice"
tree[2] = "Bob"
tree[3] = "Charlie"
# 查询数据
print(tree[2]) # 输出 Bob
# 关闭 B+ 树
tree.close()
✅ 支持磁盘存储
✅ 适用于 Python 项目
可以使用 stx::btree
:
#include
#include "stx/btree_map.h"
int main() {
stx::btree_map<int, std::string> bplus_tree;
// 插入数据
bplus_tree[1] = "Alice";
bplus_tree[2] = "Bob";
bplus_tree[3] = "Charlie";
// 查询数据
std::cout << "ID=2 对应的值:" << bplus_tree[2] << std::endl;
return 0;
}
✅ 适用于嵌入式、数据库开发
如果你想深入理解 B+ 树的工作原理,可以自己实现一个。以下是 Java 版 B+ 树 实现:
import java.util.*;
class BPlusTreeNode {
boolean isLeaf;
List<Integer> keys;
List<BPlusTreeNode> children;
List<String> values; // 叶子节点存储值
BPlusTreeNode next; // 叶子节点之间的链表
public BPlusTreeNode(boolean isLeaf) {
this.isLeaf = isLeaf;
this.keys = new ArrayList<>();
this.children = new ArrayList<>();
this.values = new ArrayList<>();
}
}
class BPlusTree {
private BPlusTreeNode root;
private int degree; // B+ 树的阶(一般取 4 或 5)
public BPlusTree(int degree) {
this.degree = degree;
this.root = new BPlusTreeNode(true);
}
public void insert(int key, String value) {
BPlusTreeNode node = root;
if (node.keys.size() == degree - 1) {
BPlusTreeNode newRoot = new BPlusTreeNode(false);
newRoot.children.add(root);
splitChild(newRoot, 0, root);
root = newRoot;
}
insertNonFull(root, key, value);
}
private void insertNonFull(BPlusTreeNode node, int key, String value) {
if (node.isLeaf) {
int pos = Collections.binarySearch(node.keys, key);
if (pos < 0) pos = -pos - 1;
node.keys.add(pos, key);
node.values.add(pos, value);
} else {
int pos = Collections.binarySearch(node.keys, key);
if (pos < 0) pos = -pos - 1;
if (node.children.get(pos).keys.size() == degree - 1) {
splitChild(node, pos, node.children.get(pos));
if (key > node.keys.get(pos)) {
pos++;
}
}
insertNonFull(node.children.get(pos), key, value);
}
}
private void splitChild(BPlusTreeNode parent, int index, BPlusTreeNode child) {
BPlusTreeNode newChild = new BPlusTreeNode(child.isLeaf);
int midIndex = (degree - 1) / 2;
parent.keys.add(index, child.keys.get(midIndex));
parent.children.add(index + 1, newChild);
newChild.keys.addAll(child.keys.subList(midIndex + 1, child.keys.size()));
child.keys.subList(midIndex, child.keys.size()).clear();
if (child.isLeaf) {
newChild.values.addAll(child.values.subList(midIndex, child.values.size()));
child.values.subList(midIndex, child.values.size()).clear();
newChild.next = child.next;
child.next = newChild;
} else {
newChild.children.addAll(child.children.subList(midIndex + 1, child.children.size()));
child.children.subList(midIndex + 1, child.children.size()).clear();
}
}
public String search(int key) {
return search(root, key);
}
private String search(BPlusTreeNode node, int key) {
int pos = Collections.binarySearch(node.keys, key);
if (node.isLeaf) {
return (pos >= 0) ? node.values.get(pos) : null;
}
if (pos < 0) pos = -pos - 1;
return search(node.children.get(pos), key);
}
}
public class BPlusTreeTest {
public static void main(String[] args) {
BPlusTree tree = new BPlusTree(4);
tree.insert(10, "Alice");
tree.insert(20, "Bob");
tree.insert(30, "Charlie");
tree.insert(40, "David");
tree.insert(50, "Eve");
System.out.println("查找 Key=30 的值: " + tree.search(30));
System.out.println("查找 Key=100 的值: " + tree.search(100)); // 不存在
}
}
方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
使用现有库 | 生产环境 | 稳定、高效 | 依赖库 |
自己实现 | 深入学习 | 可定制 | 代码复杂 |
如果你是学习 B+ 树,推荐 自己实现。如果是实际开发,建议直接使用现有库,如 MVStore
、stx::btree_map
等。
这取决于具体的业务需求和数据特点。一般来说,B+ 树适用于以下缓存场景:
但如果数据只需要简单的 KV 读取,可以用 HashMap、LRU 缓存、Redis 代替 B+ 树,避免不必要的复杂性。
假设一个 电商平台 需要缓存最近 10000 条订单数据,并支持:
orderId=12345
)。2024-03-01
~ 2024-03-05
的订单)。适合 B+ 树,因为:
✅ 有序存储,支持范围查询(优于 HashMap)
✅ 按时间排序,自动维护数据新鲜度(删除旧订单)
✅ 减少磁盘 I/O,提高查询效率
使用 Java B+ 树 + LRU 策略 实现 内存缓存,并定期清理过期订单。
class Order {
int orderId;
long timestamp; // 订单时间戳
String details;
public Order(int orderId, long timestamp, String details) {
this.orderId = orderId;
this.timestamp = timestamp;
this.details = details;
}
}
import java.util.*;
class BPlusTreeNode {
boolean isLeaf;
List<Long> keys; // 时间戳(排序用)
List<BPlusTreeNode> children;
List<Order> values; // 叶子节点存储订单数据
BPlusTreeNode next; // 叶子节点链表(范围查询)
public BPlusTreeNode(boolean isLeaf) {
this.isLeaf = isLeaf;
this.keys = new ArrayList<>();
this.children = new ArrayList<>();
this.values = new ArrayList<>();
}
}
class BPlusTreeCache {
private BPlusTreeNode root;
private int degree;
private int maxCacheSize = 10000; // 最多缓存 10000 条订单
private int currentSize = 0;
public BPlusTreeCache(int degree) {
this.degree = degree;
this.root = new BPlusTreeNode(true);
}
public void insert(long timestamp, Order order) {
if (root.keys.size() == degree - 1) {
BPlusTreeNode newRoot = new BPlusTreeNode(false);
newRoot.children.add(root);
splitChild(newRoot, 0, root);
root = newRoot;
}
insertNonFull(root, timestamp, order);
// 超过最大缓存数量,删除最早的订单
if (currentSize > maxCacheSize) {
removeOldestOrder();
}
}
private void insertNonFull(BPlusTreeNode node, long key, Order order) {
if (node.isLeaf) {
int pos = Collections.binarySearch(node.keys, key);
if (pos < 0) pos = -pos - 1;
node.keys.add(pos, key);
node.values.add(pos, order);
currentSize++;
} else {
int pos = Collections.binarySearch(node.keys, key);
if (pos < 0) pos = -pos - 1;
if (node.children.get(pos).keys.size() == degree - 1) {
splitChild(node, pos, node.children.get(pos));
if (key > node.keys.get(pos)) {
pos++;
}
}
insertNonFull(node.children.get(pos), key, order);
}
}
private void splitChild(BPlusTreeNode parent, int index, BPlusTreeNode child) {
BPlusTreeNode newChild = new BPlusTreeNode(child.isLeaf);
int midIndex = (degree - 1) / 2;
parent.keys.add(index, child.keys.get(midIndex));
parent.children.add(index + 1, newChild);
newChild.keys.addAll(child.keys.subList(midIndex + 1, child.keys.size()));
child.keys.subList(midIndex, child.keys.size()).clear();
if (child.isLeaf) {
newChild.values.addAll(child.values.subList(midIndex, child.values.size()));
child.values.subList(midIndex, child.values.size()).clear();
newChild.next = child.next;
child.next = newChild;
} else {
newChild.children.addAll(child.children.subList(midIndex + 1, child.children.size()));
child.children.subList(midIndex + 1, child.children.size()).clear();
}
}
public List<Order> rangeQuery(long start, long end) {
List<Order> result = new ArrayList<>();
BPlusTreeNode node = root;
// 定位到最左侧符合范围的叶子节点
while (!node.isLeaf) {
int pos = Collections.binarySearch(node.keys, start);
if (pos < 0) pos = -pos - 1;
node = node.children.get(pos);
}
// 遍历叶子节点,查找符合条件的订单
while (node != null) {
for (int i = 0; i < node.keys.size(); i++) {
if (node.keys.get(i) >= start && node.keys.get(i) <= end) {
result.add(node.values.get(i));
} else if (node.keys.get(i) > end) {
return result;
}
}
node = node.next;
}
return result;
}
private void removeOldestOrder() {
BPlusTreeNode node = root;
while (!node.isLeaf) {
node = node.children.get(0);
}
if (!node.keys.isEmpty()) {
node.keys.remove(0);
node.values.remove(0);
currentSize--;
}
}
}
public class BPlusTreeCacheTest {
public static void main(String[] args) {
BPlusTreeCache cache = new BPlusTreeCache(4);
// 插入订单
cache.insert(1711413240L, new Order(1001, 1711413240L, "iPhone 15"));
cache.insert(1711415000L, new Order(1002, 1711415000L, "MacBook Pro"));
cache.insert(1711418000L, new Order(1003, 1711418000L, "AirPods"));
// 查询时间范围内的订单
List<Order> orders = cache.rangeQuery(1711413000L, 1711416000L);
for (Order order : orders) {
System.out.println("订单 ID:" + order.orderId + " 商品:" + order.details);
}
}
}
方案 | 是否适用 |
---|---|
B+ 树缓存 | ✅ 适用于有序数据 + 范围查询 |
Redis Hash | ❌ 不支持范围查询 |
Redis SortedSet | ✅ 适用于排行榜、时间序列 |
HashMap | ❌ 无序存储,不适合范围查询 |
✅ 适用 B+ 树的缓存场景:
如果 数据无序且查询只需单点 Key 查询,Redis、HashMap 更高效。