• 重生之 MySQL B+Tree 提前问世二十年,MySQL之父叫我师父

重生之 MySQL B+Tree 提前问世二十年,MySQL之父叫我师父

2025-04-27 10:41:18 栏目:宝塔面板 54 阅读

二叉树的致命缺陷

场景还原:商品分类表prod_category的父 ID 字段索引。

CREATE TABLE prod_category (
  id INT PRIMARY KEY,
  parent_id INT,
  INDEX idx_parent (parent_id) -- 二叉树实现
);

性能灾难:当层级超过 10 层时,查询子类目需要递归遍历:

// 原始递归代码(林渊的注释版)
public List findChildren(Long parentId) {
  // WARNING! 每次递归都是一次磁盘寻道(8ms)
  List children = jdbc.query("SELECT id FROM prod_category WHERE parent_id=?", parentId);
  for (Long child : children) {
    children.addAll(findChildren(child)); // 指数级IO爆炸
  }
  return children;
}

林渊的实验数据(2004 年实验报告节选):

数据量

树高

平均查询时间

1 万

14

112ms

10 万

17

136ms

100 万

20

160ms

二叉树的机械困境与复杂度陷阱

图片

1970 年代,二叉搜索树(BST)的理论时间复杂度 O(logN)掩盖了物理实现的致命缺陷。以机械硬盘为例:

  • 树高灾难:100 万数据产生约 20 层高度(log₂(1,000,000)=19.9),假设每次 IO 耗时 8ms,单次查询需 160ms
  • 局部性原理失效:随机磁盘寻道导致缓存命中率趋近于 0。

另外,当所有节点都偏向一侧时,二叉树退化为“链表”。

图片

B Tree


林渊的竞争对手 Jake 说:我设计了一个 B Tree,相信是绝世无双的设计。

B 树通过多路平衡设计解决了磁盘 IO 问题。根据《数据库系统实现》的公式推导,B 树的阶数 m 与磁盘页大小的关系为:

m ≥ (PageSize - PageHeader) / (KeySize + PointerSize)

以 MySQL 默认 16KB 页为例(PageHeader 约 120 字节,KeySize 8 字节,Pointer 6 字节),阶数 m≈(16384-120)/(8+6)=1162。

10 万数据量下,B 树高度仅需 2 层(log₁₁₆₂(100000)≈2),查询 IO 次数从 17 次降为 3 次(根节点常驻内存),总延迟 24ms。

首先定义一条记录为一个二元组[key, data] ,key 为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key 值互不相同。

B-Tree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 B-Tree:

图片

每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。

两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。

以根节点为例,关键字为 17 和 35,P1 指针指向的子树的数据范围为小于 17,P2 指针指向的子树的数据范围为 17~35,P3 指针指向的子树的数据范围为大于 35。

模拟查找关键字 29 的过程:

  1. 根据根节点找到磁盘块 1,读入内存。【磁盘 I/O 操作第 1 次】
  2. 比较关键字 29 在区间(17,35),找到磁盘块 1 的指针 P2。
  3. 根据 P2 指针找到磁盘块 3,读入内存。【磁盘 I/O 操作第 2 次】
  4. 比较关键字 29 在区间(26,30),找到磁盘块 3 的指针 P2。
  5. 根据 P2 指针找到磁盘块 8,读入内存。【磁盘 I/O 操作第 3 次】
  6. 在磁盘块 8 中的关键字列表中找到关键字 29。

分析上面过程,发现需要 3 次磁盘 I/O 操作,和 3 次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。

B+Tree


林渊反驳:“每个节点中不仅包含数据的 key 值,还有 data 值。

而每一个页的存储空间是有限的,如果 data 数据较大时将会导致每个节点(即一个页)能存储的 key 的数量很小,当存储的数据量很大时同样会导致 B-Tree 的深度较大,增大查询时的磁盘 I/O 次数,进而影响查询效率。”

而且 BTree 的非叶子节点存储数据,导致范围查询需要跨层跳跃。

林渊脑海中立马翻阅在 2025 年学到的 B+Tree 数据结构,在《MySQL 内核:InnoDB 存储引擎》中发现这段代码:

// storage/innobase/btr/btr0btr.cc
void btr_cur_search_to_nth_level(...) {
  /* 只有叶子节点存储数据 */
  if (level == 0) {
    page_cur_search_with_match(block, index, tuple, page_mode, &up_match,
                              &up_bytes, &low_match, &low_bytes, cursor);
  }
}

"原来 B+树通过叶子层双向链表,把离散的磁盘页变成了连续空间!"

整理好思绪,继续补充道:B+树通过以下创新实现质的飞跃:

  1. 全数据叶子层:所有数据仅存储在叶子节点,非叶节点仅作索引目录
  2. 双向链表串联:叶子节点通过指针形成有序链表,范围扫描时间复杂度从 O(logN)降为 O(1)。

在 B+Tree 中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储 key 值信息,这样可以大大加大每个节点存储的 key 值数量,降低 B+Tree 的高度。

B+Tree 的非叶子节点只存储键值信息,假设每个磁盘块能存储 4 个键值及指针信息,则变成 B+Tree 后其结构如下图所示:

图片

通常在 B+Tree 上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。

因此可以对 B+Tree 进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。

MysQL 之父眼睛冒光,看着我惊呆了!!恨不得叫我一声大师。

西湖论剑,单挑首席

一月后,全球数据库峰会在西子湖畔召开。林渊抱着一台 IBM 服务器走上讲台:"给我 30 秒,让各位见见‘未来索引’!”

实时 PK 表演:

-- 场景:1亿订单数据查询
-- 传统B树(甲骨文)
SELECT * FROM orders WHERE id BETWEEN 100000 AND 200000;
-- 耗时12.8秒

-- B+树(林渊魔改版)
SELECT /*+ BPLUS_SCAN */ * FROM orders BETWEEN 100000 AND 200000;
-- 耗时0.3秒

名场面台词:

"诸位,这不是优化,是维度的碾压!

B+树把磁盘的物理运动,变成了内存的闪电舞蹈!"——当日登上《程序员》杂志封面。三月后,林渊成立"深空科技",发布"伏羲 B+引擎"。

美国商务部紧急会议:"绝不能让中国掌控数据库心脏!"

本文地址:https://www.yitenyun.com/102.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL 数据同步 ACK Web 应用 异步数据库 双主架构 循环复制 Deepseek 宝塔面板 Linux宝塔 Docker 生命周期 序列 核心机制 JumpServer JumpServer安装 堡垒机安装 Linux安装JumpServer esxi esxi6 root密码不对 无法登录 web无法登录 Windows Windows server net3.5 .NET 安装出错 宝塔面板打不开 宝塔面板无法访问 SSL 堡垒机 跳板机 HTTPS 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 无法访问宝塔面板 Windows宝塔 Mysql重置密码 HTTPS加密 连接控制 机制 ES 协同 Oracle 处理机制 Serverless 无服务器 语言 OB 单机版 Spring SQL 动态查询 scp Linux的scp怎么用 scp上传 scp下载 scp命令 技术 存储 索引 运维 缓存方案 缓存架构 缓存穿透 架构 InnoDB 日志文件 MIXED 3 分页查询 Rsync 响应模型 修改DNS Centos7如何修改DNS RocketMQ 长轮询 配置 监控 HexHub Redis 电商 系统 Linux 安全 服务器 MySQL 9.3 聚簇 非聚簇 异步化 防火墙 黑客 查询 数据 主库 sftp 服务器 参数 group by 自定义序列化 数据库锁 SQLark PostgreSQL 管理口 业务 SQLite-Web SQLite 数据库管理工具 开源 存储引擎 Doris SeaTunnel MVCC 人工智能 向量数据库 推荐系统 R edis 线程 线上 库存 预扣 共享锁 网络架构 工具 网络配置 加密 场景 流量 数据备份 PG DBA AI 助手 Ftp redo log 重做日志 ​Redis 机器学习 推荐模型 信息化 智能运维 B+Tree ID 字段 • 索引 • 数据库 缓存 高可用 Canal GreatSQL 连接数 Python 优化 万能公式 RDB AOF Redis 8.0 核心架构 订阅机制 网络故障 Postgres OTel Iceberg INSERT COMPACT Hash 字段 同城 双活 云原生 IT运维 微软 SQL Server AI功能 不宕机 自动重启 Web prometheus Alert 引擎 性能 大模型 SVM Embedding 高效统计 今天这篇文章就跟大家 Netstat Linux 服务器 端口 分库 分表 虚拟服务器 虚拟机 内存 向量库 Milvus OAuth2 Token Entity 开发 Undo Log 单点故障 DBMS 管理系统 MongoDB 容器 sqlmock 崖山 新版本 ZODB 数据集成工具 容器化 LRU mini-redis INCR指令 JOIN 窗口 函数 分布式 集中式 悲观锁 乐观锁 openHalo 磁盘架构 SpringAI 排行榜 排序 Testcloud 云端自动化 Redka 数据脱敏 加密算法 Redisson 锁芯 QPS 高并发 EasyExcel MySQL8 Recursive PostGIS 启动故障 大表 业务场景 分页 数据结构 意向锁 记录锁 聚簇索引 非聚簇索引 数据类型 StarRocks 数据仓库 分布式架构 分布式锁​ Pottery 模型 原子性 AIOPS R2DBC 事务 Java SSH IT Caffeine CP MCP 开放协议 InfluxDB 1 发件箱模式 网络 Web 接口 dbt 数据转换工具 RAG HelixDB 工具链 池化技术 连接池 数据分类 部署 filelock 对象 速度 服务器中毒 字典 读写 Go 数据库迁移 事务隔离 传统数据库 向量化 优化器 单线程 仪表盘 双引擎 分页方案 排版 数据页 Order 频繁 Codis LLM 线程安全 Crash 代码 List 类型 订单 Pump 事务同步 UUIDv7 主键 日志 Ansible