【数据库】cmu15445-project2 B+Tree Checkpoint 1 实验总结
project-2相比project-1难度提升了不少。
project2的工作量较大,因此分成两个checkpoint。checkpoint2是支持并发安全,而checkpoint1其实是数据结构的问题,这篇文章先写project-1的checkpoint1。
实验前提
可以看project-2的实验文档。由于课件里没有B+ Tree的具体插入删除规则,因此需要找数据库系统概念找到对应的伪代码,最好看英文版的内容。整个project的checkpoint1就是在这个数据结构上折腾。
实验内容
总体实现的接口
checkpoint1需要实现tree如下接口:Remove Insert GetValue
迭代器接口:Begin End operator++
整个project的任务(主要数据结构已经搭好了框架,具体的增加和删除接口要自己实现)如下:
Task #2a - B+Tree Insertions
Task #2b - B+Tree Deletions
Task #2c – Index Iterator
Task #2d – Concurrency Control
对应的leaf节点和internal节点类的实现按照需要添加接口
1.核心伪代码
1.1 插入
2.删除:
2.数据结构的数量关系
由于两个主要流程(删除和插入)的代码确实比较长,所以在实现之前最好先搞清楚数据存储的结构,不要遗留太多问题留在实现的时候:
叶子节点跟内部节点存在最大size,最小size值的区别;
内部节点的value都是page_id,叶子节点的value是个模板类型,支持泛型。
内部节点因为其子节点数=kv对数+1,所以用kv对的数组保存的时候,会有一个kv对没有对应的key,为了方便把index=0的指针设为无效。
叶子节点的结构:
/*
+----------+------+----------+------+-----+-------------+----------+----------+
| Pointer1 | Key1 | Pointer2 | Key2 | ... | Pointer(N-1)| Key(N-1) | PointerN |
+----------+------+----------+------+-----+-------------+----------+----------+
*/
内部节点的结构:
对于任意一个内部节点,其子树中,满足K(i) <= K < K(i+1),其中i为内部节点的页号;i+1为i节点的下一个兄弟节点;这里n=size-1,KEY(0)无效
/*
+----------+------+----------+------+-----+-------------+----------+----------+
| KEY(0)+PAGE_ID(0) | KEY(1)+PAGE_ID(1) | KEY(2)+PAGE_ID(2) | ... | KEY(n)+PAGE_ID(n) |
+----------+------+----------+------+-----+-------------+----------+----------+
*/
数量关系遵循如下约定:
internal的size=key数+1=指针数;
leaf的size=key数=value数;
节点类型与size大小 |
叶子节点 |
内部节点 |
最小值 |
作为根节点=1;非根节点=floor(max_size/2) |
作为根节点=2,非根节点=floor((max_size+ 1) / 2) |
最大值 |
leaf_max_size |
internal_max_size |
key数 |
size |
size – 1 |
插入的分裂条件 |
插入后size>=leaf_max_size |
插入后size>internal_max_size |
删除的合并条件 |
删除后与另一节点的size之和∈[min_size, max_size) |
插入后与兄弟节点的size之和∈[min_size, max_size] |
删除的重新分布条件(拆借) |
删除后与另一节点的size之和>=max_size |
插入后与兄弟节点的size之和>max_size |
由于internal的size跟leaf的size表示的内容其实都是以数组大小为准,所以判断分裂条件和其他的时候都有差为1的调整。
其中,叶子节点最小的max_size可以是2,而内部节点最小的max_size是3。否则它不满足B+树的形态,空叶子节点和只有1个孩子的内部节点是不合法的,此时需要做删除、合并的调整。
3. 拆借
插入的分裂和删除的拆借是不同的。分裂之后节点的元素均匀分布,而拆借只需从兄弟节点取得1个节点即可。
3.二分法的细节
节点的查找、删除都用到了节点内部的key有序的性质,所以有好几个函数会出现二分查找的要求。但是查找要注意查找失败的时候要返回什么,这里建议一开始先确定并注释Lookup,Index等函数的含义,如返回未找到的下一个index(类似C++里的lower_bound)。
而对于根据page_id查找叶子节点的要求(寻找兄弟节点),则一般靠遍历。这里没有测试过性能的差别,但是page_id遍历比较的性能应该与二分法比较多个key的性能差不多。虽然O(n)的复杂度比较高,但是由于实际应用中平均查找长度只有n/2,而且指针页号的比较为int类型,未必比key类型的O(logN)比较慢。
实验技巧
1.debug技巧
在第一次实现的过程中大概率会遇到很多问题。为了便于调试,建议写一个dump的函数,输出B+树的形状,这样就可以直观地看清楚
成员的变化,降低debug的难度。
同时使用官方提供的printer可以对照发现自己实现的问题。
2.测试用例技巧
对于插入,删除,除了简单的批量插入,批量删除,还可以用以下的方式,构造随机插入删除的数组。
References
https://15445.courses.cs.cmu.edu/fall2022 课程官网
https://github.com/cmu-db/bustub Bustub Github Repo
Database System Concepts 6th version, Abraham.Silberschatz.
自动测评网站 GradeScope,course entry code: PXWVR5 https://www.gradescope.com/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」