☰
MiniOB 完整导读:不到 10 万行代码,把 B+ 树索引、事务和 SQL 执行链路一次讲透
2026/10/19 1:12:04 网站建设 项目流程

MiniOB 完整导读:不到 10 万行代码,把 B+ 树索引、事务和 SQL 执行链路一次讲透

【免费下载链接】miniobMiniOB is a compact database that assists developers in understanding the fundamental workings of a database.项目地址: https://gitcode.com/GitHub_Trending/mi/miniob

MiniOB 是 OceanBase 团队做的一个教学用关系型数据库,用 C++ 写成,支持建表、插数、查询、B+ 树索引和 MVCC 事务。它把真实数据库里最核心的模块都做了一遍,但刻意砍掉了安全、高可用这些干扰项,代码量控制在 10 万行以内。读完本文,你会知道怎么把它编译跑起来、一条 SQL 进去之后内部走了哪几步、B+ 树索引的源码在哪里改,最后还有一个一小时内能完成的小练习。

🚀 先跑起来:一条命令完成构建

环境要求写在 docs/docs/how_to_build.md 里:cmake 3.13+、GCC 11 或 Clang 14 以上、flex 和 bison,都是常见版本,Linux 和 macOS 都适用。

git clone https://gitcode.com/GitHub_Trending/mi/miniob cd miniob && bash build.sh init && bash build.sh cd build_debug ./bin/observer -f ../etc/observer.ini -P cli

build.sh init只跑一次,把第三方依赖装到deps/3rd/usr/local下;后面的build.sh默认编译 debug 版。启动参数-P cli表示直接交互模式,不用另开客户端,这是新手最省事的姿势。进去之后直接敲create table t1 (a int, b int);,再select * from t1;,看到空结果集说明服务已经正常。想体验客户端模式的话,用-p 6789启动 observer,另开终端跑./bin/obclient -p 6789,具体参数见 docs/docs/how_to_run.md。

🛤️ 跟一条 SQL 走到黑:执行链路拆成四步

你敲下的select * from t1 where a > 1;不会一步到位,它要在系统里接力传递。以一条带 where 条件的 select 为例,链路是这样的:

  1. 收包与调度。网络模块收到请求,交给 TaskHandler,它会为这条 SQL 建一个事件对象,并绑定当前会话。对应代码在 src/observer/net/。
  2. 解析。Flex + Bison 做词法和语法分析,把 SQL 文本变成抽象语法树(AST)。词法规格在 src/observer/sql/parser/ 里的.l和.y文件里,想搞清楚"语法到底怎么定义的",翻这两个文件比看任何博客都快。
  3. 语义分析。Resolver 遍历 AST,检查表和列是否存在、类型是否匹配,然后生成内部的 Stmt 对象(比如 SelectStmt),代码在 src/observer/sql/stmt/。
  4. 优化与执行。Optimizer 根据规则调整执行计划,Executor 按算子树执行——表扫描、条件过滤这些都在 src/observer/sql/operator/ 下,一个算子一个文件,结构非常清晰。

这四个阶段由 SessionStage 用责任链模式串起来,入口在 src/observer/session/session_stage.cpp,顺着handle_request一路往下跟,链路自然就出来了。

🔍 拆一个模块:B+ 树索引怎么把键变成磁盘页

B+ 树是 MiniOB 的默认索引结构,也是数据库里最经典的磁盘数据结构。它要解决的问题是:表数据动辄几百兆,凭什么一次查询只读几次磁盘就能定位到一行?

答案藏在页的设计里。每个结点就是一个固定大小的 page,叶结点按顺序存放"索引列值 + RID"(RID 指向数据行在磁盘上的位置),并且用prev_brother/next_brother把相邻叶结点串成链表,所以等值查询和范围查询都走同一套叶子路径。内部结点不存数据,只存"键 + 子结点 page 号",查询从根页出发逐层往下,每层只读一个 page。

插入时叶结点满了就分裂:新建右兄弟、键值对对半搬、把新结点最小键上提;如果一路向上都满,就新建根页让树长高。删除方向则相反,结点键值对少于一半时触发兄弟合并或重构。设计文档 docs/docs/design/miniob-bplus-tree.md 里每一步都配了图,实现代码在 src/observer/storage/index/,主文件是bplus_tree.cpp。另外src/oblsm/下还有一套完整的 LSM-Tree 引擎(memtable + sstable + compaction),想换一种索引思路可以从那边走进去。

🏋️ 一个小练习:一小时内读懂 B+ 树的插入路径

任务是跑通 B+ 树单元测试,再顺着一次插入把源码跟一遍,不用写新代码。

  1. 确认已经用bash build.sh编译过,单测目标默认开启(CMake 里的WITH_UNIT_TESTS)。
  2. 在build_debug目录下执行ctest -R bplus_tree,会运行bplus_tree_test,它覆盖插入、删除、页分裂和日志恢复等场景。
  3. 测试入口在 unittest/observer/bplus_tree_test.cpp,看test_insert函数怎么构造 RID 再往树里灌数据。
  4. 打开 src/observer/storage/index/bplus_tree.cpp,找到插入和分裂相关的函数,对照设计文档里的分裂图,确认"新结点最小键上提到父结点"这一步在代码里的位置。

做完这一轮,你对"索引在磁盘上长什么样"应该就有具体画面了。想继续加难度,可以去 docs/docs/db_course_lab/ 看官方实验题,lab1 到 lab4 分别是 LSM 存储、查询引擎、事务引擎和性能测试。

❓ 踩坑 FAQ

问:跑build.sh报build.sh: line 2: $'\r': command not found?典型的 Windows 换行符污染,脚本里的 CRLF 被带进来了。先执行git config --global core.autocrlf false,再用git checkout -- .把工作区文件重写一遍,重新构建即可。

问:启动 observer 提示找不到动态链接库?依赖默认装在/usr/local下,而它不一定在你的动态库搜索路径里。把export LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH加进~/.bashrc然后source ~/.bashrc,重启程序。

问:想打断点看某条 SQL 的执行,入口在哪?调试方法写在 docs/docs/dev-env/miniob-how-to-debug.md。最快的一条路是gdb -p \pidof observer`挂上已启动的服务,然后对do_select或Table::scan_record` 下断点,单步就能看见 select 从上层算子一路落到存储层的过程。


MiniOB 的代码结构对读源码非常友好:一层目录对应一个模块,一个文件基本对应一个算子或一类页结构,配合设计文档可以按图索骥。建议的节奏是先让服务跑起来,再挑本文这条 SQL 链路跟一遍,最后用 B+ 树单测收口,内核的主要骨架就摸清了。

资源索引:

  • 官方文档目录:docs/docs/,架构总览在 docs/docs/design/miniob-architecture.md
  • 服务端源码入口:src/observer/main.cpp,SQL 与存储分别在 src/observer/sql/ 和 src/observer/storage/
  • 单元测试:unittest/,集成测试:test/integration_test/

【免费下载链接】miniobMiniOB is a compact database that assists developers in understanding the fundamental workings of a database.项目地址: https://gitcode.com/GitHub_Trending/mi/miniob

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询