恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

MiniOB数据库内核实战:从SQL解析到存储引擎的构建与调试

  • 首页
  • 资讯中心
  • /
  • MiniOB数据库内核实战:从SQL解析到存储引擎的构建与调试

相关资讯

Prettier统一编码风格:自动化格式化的工程实践指南 2026/10/13 9:51:18
从 ABAP SQL 到远程数据库,彻底理解 CDS External Entity 的 Outbound SQL 访问链路与运行时对象 2026/10/13 9:51:18
Redisson集成Spring Boot:原始依赖与Starter的选型与避坑指南 2026/10/13 9:51:17

最新资讯

双馈与永磁风电机组并网短路故障建模与仿真分析
GitHub 开源热榜项目-周榜(2026-10-12)
SpringBoot+Vue3美食互动平台开发实战:从数据库设计到部署避坑
实战型网安赛事与传统CTF有何不同?破阵阁公开赛参赛全记录
Unity跑酷游戏开发:状态机+对象池+动态难度实战
数据结构三要素与链表12个基础操作:从理论到C语言实战

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

MiniOB数据库内核实战:从SQL解析到存储引擎的构建与调试

发布时间:2026/10/13 9:51:18
MiniOB数据库内核实战:从SQL解析到存储引擎的构建与调试 简介基于C的MiniOB数据库系统是一套面向数据库初学者的入门实践源码包由OceanBase与华中科技大学联合打造帮助零基础学习者理解数据库内核各模块的功能与关联并训练设计高效SQL。压缩包内共363个文件以头文件(h)、C源文件(cpp)为主体另有Markdown文档、PNG示意图、测试结果与脚本等压缩包大小仅3.17MB适合快速下载与本地调试。当前已有74人浏览/学习内容按模块化设计组织覆盖INI配置解析、IO操作、互斥锁、日志系统、索引管理、记录管理等基础功能。从内容预览可见包含词法/语法分析、B树、表管理、磁盘缓冲池等关键源码便于读者对照实际工程理解数据库内核的实现脉络。对于希望从SQL使用深入到内核源码的初学者这份资源提供了清晰的学习路径和可运行的参考实现。1. MiniOB不是玩具一门能写进简历的数据库内核课很多人的数据库学习路径是《数据库系统概论》加一个JDBC连MySQL的小作业最后对“B树分裂”“两阶段锁”的理解停留在考试名词上。MiniOB是OceanBase团队开源的一个教学数据库项目它把数据库内核的五个核心模块——SQL解析、执行器、存储引擎、事务与并发控制、网络协议——全部用C实现了一遍代码量在2万行上下比看一本黑皮书直观得多。我的建议很直接与其花几个月从零写一个学习型数据库不如基于MiniOB的源码去改造、调试、扩展把它变成你自己的作品最后写进简历。适合正在准备数据库内核岗位校招、做课程设计、或者纯粹想搞清楚“一条SELECT到底经历了什么”的C开发者。2. 先把工程跑起来MiniOB的依赖、构建与调试环境2.1 依赖清单别再绕开flex和bisonMiniOB的语法解析不是手写递归下降而是用flex生成词法分析器、bison生成语法分析器这是它第一个容易劝退新手的地方。很多人在Windows上用Visual C跑这个项目卡在第一步——flex和bison在Windows下没有官方二进制需要借助winflex-bison这个第三方移植包。我在Ubuntu 22.04下的安装命令是这样sudo apt update sudo apt install -y gcc g cmake make flex bison libevent-dev libjsoncpp-dev参数说明flex和bison是解析器生成工具MiniOB的src/observer/sql/parser目录下存放的是.l和.y文件CMake构建时会调用这两个工具自动生成词法分析与语法分析的C源码。libevent是网络框架依赖MiniOB的observer进程用libevent处理客户端连接。libjsoncpp用于部分配置解析和结果序列化。缺了任何一个依赖CMake配置阶段就会报错。不建议用apt install default-libevent-dev在某些发行版上这个元包指向旧版本编译时会出现事件循环接口不匹配。直接指定libevent-dev更稳。2.2 构建与配置文件一次成功的编译顺序拿到源码包后不要直接cmake ..先看一眼src/observer/CMakeLists.txt了解目标结构然后按标准流程构建cd ~/minidb mkdir -p build cd build cmake .. -DCMAKE_BUILD_TYPEDebug make -j$(nproc) observer参数说明CMAKE_BUILD_TYPEDebug必须带上。这个项目一旦开O2优化断点处变量被优化掉调试时你会看到一个变量“无法读取”的黑匣子状态。-j$(nproc)是并行编译第一次全量编译大概需要两三分钟。observer是服务端目标编译产物在build/bin目录下。编译成功后运行前需要初始化数据目录。MiniOB默认从etc/observer.ini读取监听端口和数据目录配置mkdir -p ~/minidb/data cd ~/minidb/build ./bin/observer -f ../etc/observer.ini配置文件里两个最关键项是端口和UNIX套接字路径。如果启动后客户端连不上第一件事是检查套接字路径是否被注释掉。我在第一次运行时踩过这个坑observer.ini里默认是unix_socket_path /tmp/miniob.sock但有些打包版本会把它注释掉导致只能走TCP连本机客户端默认又走unix socket结果互相找不到。2.3 用VSCode调试配置C/C环境的正确姿势MiniOB是单机observer 客户端cli的架构调试时最怕的就是服务端崩溃、客户端没有输出。我的习惯是用VSCode配合C/C插件做远程调试launch.json关键配置如下{ name: MiniOB Debug, type: cppdbg, request: launch, program: ${workspaceFolder}/build/bin/observer, args: [-f, ${workspaceFolder}/etc/observer.ini], cwd: ${workspaceFolder}, environment: [], MIMode: gdb, setupCommands: [ { description: Enable pretty-printing, text: -enable-pretty-printing, ignoreFailures: true } ], preLaunchTask: build-observer }preLaunchTask需要在tasks.json里定义作用是每次调试前自动执行一次增量编译。args里的路径最好用${workspaceFolder}拼接不要写死绝对路径换机器后不用改。调试时在SqlTask::execute里打断点客户端输入select * from t;就能看到完整执行链路。3. 从SQL到执行计划MiniOB的解析与执行链路3.1 一条SELECT的完整旅程MiniOB的SQL执行分成四个阶段词法分析、语法分析、逻辑计划生成、执行。词法分析器把SQL文本切成一串tokenbison根据.y文件里的语法规则把这些token组织成一棵语法树。这棵语法树经过resolve阶段转化成带类型信息的SelectSqlNode再交给SelectStmtExecutor执行。实际执行时SelectStmtExecutor::execute做的是先通过Database::find_table定位表对象再创建TableScanner遍历存储引擎里的记录对每条记录调用Expression::evaluate计算过滤条件。整个过程没有生成真正的物理计划树MiniOB把“逻辑计划优化”这层砍掉了选择直接解释执行。这对学习是好事——你能在execute函数里看到每个算子的完整逻辑不会被优化器绕晕。常见做法是拿到源码后第一件事不是读README而是从一条SQL出发用调试器跟踪一遍它经过的每个函数。我一般会关注这三个函数parse在yacc生成的代码里、SelectStmtExecutor::execute、TableScanner::next。它们分别对应了解析、执行、存储三个层面。3.2 表达式求值看懂这一块就读懂了一半执行器SELECT、WHERE、JOIN条件在MiniOB里都归结为表达式求值。Expression有多个派生类FieldExpr字段引用、ValueExpr常量、ComparisonExpr比较、ArithmeticExpr加减乘除。求值核心在evaluate方法简化后的逻辑如下// src/observer/sql/expr/expression.cpp 中的简化流程 Value ComparisonExpr::evaluate(const Row row) { // 左操作数可能是字段、常量或另一个表达式 Value left left_-evaluate(row); Value right right_-evaluate(row); // 类型比较前先做隐式转换INT和FLOAT统一转FLOAT if (left.attr_type() ! right.attr_type()) { left Value::cast_to(left, right.attr_type()); } bool result false; switch (comp_) { case EQUAL: result (left.compare(right) 0); break; case LESS_THAN: result (left.compare(right) 0); break; // ... 其他比较操作符 } return Value(result); }关键在compare函数。MiniOB里不同字段类型走不同的比较路径VARCHAR比较用的是strncmp配合length()而不是strcmp——这两个的差异会在后面避坑章节细讲。Value是所有类型的统一封装内部维护attr_type_和data_类似一个极简版std::variant。所以你看到代码里频繁出现类型转换函数时不用慌这是数据库引擎的常规操作。3.3 怎么判断你要改哪个文件当你打算给MiniOB加功能先看功能落在哪一层。加新语法比如CREATE INDEX要改三处.l文件加关键字、.y文件加语法规则、parse_defs.h加语法树节点。加执行逻辑比如排序只需要改SelectStmtExecutor。加存储能力比如索引要同时动Table、RecordFileHandler和新的Index实现。这个分层判断比盲目读源码高效得多。做课程设计的学生最喜欢问“从哪里下手”我一般会建议从一个最薄的点切入给WHERE条件加一个LIKE操作符。它不涉及存储层只需要在lexer里加关键字、parser里加语法分支、ComparisonExpr里加一个比较类型。4. 存储引擎与并发record结构、事务与锁的实现边界4.1 record的物理布局理解堆表组织方式MiniOB的表是典型的堆表Heap Table数据页大小是4KBBP_PAGE_SIZE。表中每行数据叫一个recordrecord的物理格式如下// src/observer/storage/record/record.h 中核心结构示意 struct Record { RecordId rid_; // 逻辑位置page_num slot_num char* data_; // 行数据缓冲区 int len_; // 行数据长度 }; struct RecordId { int page_num; // 页码 int slot_num; // 槽位号 };rid_是整个存储引擎的定位核心。读取一条记录时先通过page_num在缓冲池中找到数据页再通过slot_num在页内定位到行偏移。删除操作是标记删除在页头位图清零空间由后续插入复用。这种设计在RecordFileHandler::insert_record里体现得很明显——它先在页位图里找空槽找不到才申请新页。4.2 记录扫描一次全表扫描是如何发生的TableScanner的实现非常朴素它内部持有一个RecordFileScanner每次next()都从当前页当前位置继续往下找。伪代码逻辑是bool RecordFileScanner::next(Record record) { while (!end_of_file_) { // 遍历当前页的所有slot跳过标记删除的记录 for (int slot current_slot_; slot slot_count_; slot) { if (page_-is_record_valid(slot)) { record.rid_ {page_num_, slot}; memcpy(record.data_, page_-data() slot_offset, record_len); return true; } } // 当前页扫完读下一页 next_page(); } return false; }这里有个明显的性能特征全表扫描时每读一条记录都要memcpy一次行数据扫描大量行时开销可观。MiniOB是教学项目不追求极致性能但这个memcpy值得记住——将来做批量读取优化时返回页内指针而不是拷贝数据是第一优先级。4.3 事务与并发控制MiniOB的取舍MiniOB真正意义上的事务支持是“单语句事务”——每条SQL语句自动提交没有显式的BEGIN/COMMIT协议。这和主流数据库差别很大也是大家在移植到生产场景时认知最容易错位的地方。并发控制层面MiniOB实现了基于锁的隔离。TrxKit管理事务上下文表上维护锁集合。读操作加共享锁、写操作加排他锁锁等待策略是不等待——获取不到锁直接返回失败。实际做并发压测时会发现两个线程交替UPDATE同一行其中一个大概率拿到LOCK_CONFLICT错误。// 事务加锁的调用路径 RC Trx::lock_record(Record* record) { LockUnit lock_unit(record-rid_); // 在锁集合里查重已有排他锁则返回失败 std::lock_guardstd::mutex guard(lock_set_mutex_); if (lock_set_.find(lock_unit) ! lock_set_.end()) { return RC::LOCK_CONFLICT; } lock_set_.insert(lock_unit); return RC::SUCCESS; }这段代码的局限性很明显锁粒度是record级别但锁集合是std::unordered_set没有锁升级、没有死锁检测、没有多版本。做课程设计时如果你想展示并发控制能力在这层做扩展比在存储层做容易得多——但要注意引入等待队列后一旦两个事务互相等锁就真的需要死锁检测了否则会卡死。具体的死锁检测实现先思考一个问题事务等待图如何维护想清楚这个再动手写代码。5. 避坑指南MiniOB编译、调试与验收的5个典型翻车点5.1 现象bison报语法冲突生成代码失败原因yacc语法文件里多个规则存在移进-归约冲突常见于在原有表达式规则上叠加新操作符时优先级声明不全。MiniOB的yacc_sql.y里通过%left声明了操作符优先级——如果新加的操作符没有声明优先级bison会默认按“移进优先”处理导致WHERE a 1 AND b 2 OR c 3这类混合条件优先级错乱。解决在.y文件中找到表达式优先级声明区域把所有二元操作符按需要的优先级排列加%left声明。改完后重新编译观察bison输出的冲突警告数量——如果归零基本就对了。5.2 现象VARCHAR字段比较结果错乱aabc匹配到了abcd原因Value::compare里VARCHAR比较沿用了strcmp语义。strcmp遇到字符串结束符就停abc和abcd比较时前者先结束返回负值——但如果代码直接比较长度后按短长度memcmp结果就会出问题。MiniOB在某些版本里用strncmp比较前min(len1, len2)个字节只要abc是abcd的前缀就会被判相等。解决改compare逻辑先比较长度长度不同直接返回不相等长度相同再用memcmp。修改位置在Value::compare的VARCHAR分支。5.3 现象多线程并发写入时插入的记录互相覆盖原因RecordFileHandler::insert_record里find_free_slot和mark_slot_used不是原子操作。事务里加了锁但对表文件的写操作没有串行化。两个事务同时找到同一个空槽位后写的覆盖先写的。解决在insert_record入口加一个表级写锁把“查找空槽”和“标记占用”放在同一临界区。课程设计的验收场景一般不会触及这个问题但如果你要展示并发能力这几乎是必踩的坑。5.4 现象observer进程段错误core dump文件巨大原因最常见的段错误来自Record的data_指针——在表达式求值过程中字段值是从record-data_里解析的char*如果这条record所属的内存被释放比如表drop后扫描器还在遍历指针就成了悬垂指针。第二种常见原因是配置了unix_socket_path后客户端传的SQL语句长度超过缓冲区。解决用gdb打开core文件bt看堆栈。第一类段错误的堆栈一定会停在Expression::evaluate或FieldExpr::evaluate附近排查时优先检查表生命周期DROP TABLE之后是否还有未结束的查询会话在扫描。我一般会在TableScanner里加一个table_open_count_引用计数析构时确保为零。5.5 现象Windows上用Visual C编译flex/bison生成代码报一堆兼容性错误原因winflex-bison生成的C代码默认带int声明和yyscan_tVC编译器对C风格的隐式类型转换比GCC更严格。还有flex生成的yywrap在VC下需要额外定义。解决推荐直接用WSL2或者装Linux虚拟机不要在Windows原生环境折腾这个项目。如果你的课程设计要求必须Windows演示退而求其次的办法是安装msys2在msys2终端里用MinGW编译然后到Windows命令行运行exe。这个方案耗时半小时左右但比在VC里改兼容性错误省时得多。6. 把MiniOB变成你的作品从改一行代码到完整自测MiniOB最尴尬的定位是“做完课程设计就删了”它本可以成为面试时最有说服力的技术谈资。我的习惯是给MiniOB增加一个“语句级执行耗时统计”功能改动很小却能体现对性能的敏感。在SessionStage::handle_request里SQL执行前后各打一个chrono::steady_clock::now()算出耗时后拼到返回给客户端的消息尾部。对select count(*)这类全表扫描语句这个数字能直观反映扫描行数与耗时的线性关系。接着做一次并发回归用脚本启动两个客户端同时对同一张表插入1000条记录。这个测试能让你的锁逻辑暴露问题——如果插入结果不满2000或者进程崩溃就说明并发控制还不过关。再跑一次gdb调试确认无内存泄漏。我自己在做这个项目时最大的教训是光看不练源码读十遍不如崩溃一次。读懂了执行器后亲手加一个函数比看完所有源码注释都更接近“我能造数据库”这个目标。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号