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

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

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

二叉树的致命缺陷

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