😂Expand
Project 2
1. 实验介绍
第一个打分点---实现b+树的基本结构、插入、搜索操作
注意这里没有考虑打分点2的并发问题,所以对于加锁、解锁和事物都没有考虑。
第二个打分点--实现b+树的删除操作、索引迭代器和对并发访问的支持
Task 1 B+TREE PAGES#
您需要实现三个页面类来存储B+树的数据。
B+ Tree Parent Page
B+ Tree Internal Page
B+ Tree Leaf Page
1. B+ Tree Parent Page
这是内部页和叶页都继承的父类,它只包含两个子类共享的信息。父页面被划分为如下表所示的几个字段。
*B+Tree Parent Page Content
page_type_
4
Page Type (internal or leaf)
lsn_
4
Log sequence number (Used in Project 4)
size_
4
Number of Key & Value pairs in page
max_size_
4
Max number of Key & Value pairs in page
parent_page_id_
4
Parent Page Id
page_id_
4
Self Page Id
必须在指定的文件中实现父页。只能修改头文件(src/include/storage/page/b_plus_tree_page.h) 和其对应的源文件 (src/storage/page/b_plus_tree_page.cpp).
这里都是一些简单的set、get就不写出来了
IsRootPage函数根据parent_id_是否是INVALID_PAGE_ID返回true或者falseGetMinSize():需要区分 1)根结点且为叶子结点 2)根结点 3)其他
2. B+TREE INTERNAL PAGE
内部页不存储任何实际数据,而是存储有序的m个键条目和m + 1个指针(也称为page_id)。
由于指针的数量不等于键的数量,因此将第一个键设置为无效,并且查找方法应始终从第二个键开始。
任何时候,每个内部页面至少有一半已满。 在删除期间,可以将两个半满页面合并为合法页面,或者可以将其重新分配以避免合并,而在插入期间,可以将一个完整页面分为两部分。
你只能修改头文件(src/include/storage/page/b_plus_tree_internal_page.h) 和对应的源文件(src/page/b_plus_tree_internal_page.cpp).
3. B+TREE LEAF PAGE
叶子页存储有序的m个键条目(key)和m个值条目(value)。
实现中,值只能是用于定位实际元组存储位置的64位record_id,请参阅src / include / common / rid.h中定义的RID类。
叶子页与内部页在键/值对的数量上具有相同的限制,并且应该遵循相同的合并,重新分配和拆分操作。
必须在指定的文件中实现内部页。 仅允许修改头文件(src / include / storage / page / b_plus_tree_leaf_page.h)及其相应的源文件(src / storage / page / b_plus_tree_leaf_page.cpp)。
‼️ 重要的KeyIndex函数
这个函数可以返回第一个>=当前key值的编号。这个在插入的时候经常会用到,这样就可以让代码重复利用,在LevelDB中也有类似的操作。
重要提示:尽管叶子页和内部页包含相同类型的键,但它们可能具有不同类型的值,因此叶子页和内部页的最大大小可能不同。
每个B + Tree叶子/内部页面对应从缓冲池获取的存储页面的内容(即data_部分)。 因此,每次尝试读取或写入叶子/内部页面时,都需要首先使用其唯一的page_id从缓冲池中提取页面,然后将其重新解释为叶子或内部页面,并在写入或删除后执行unpin操作。
Task 2.A - B+TREE DATA STRUCTURE (INSERTION & POINT SEARCH)
其实就是实现
b_plus_tree.cpp/InsertIntoLeaf函数所涉及到的相关函数。
这里 B +树索引只能支持唯一键。 也就是说,当您尝试将具有重复键的键值对插入索引时,它应该返回false。
对于checkpoint1,仅需要B + Tree索引支持插入(Insert)和点搜索(GetValue)。 我们不需要实现删除操作。
插入后如果当前键/值对的数量等于max_size,则应该正确执行分割。 由于任何写操作都可能导致B + Tree索引中的root_page_id发生更改,因此要更新(src / include / storage / page / header_page.h)中的root_page_id,以确保索引在磁盘上具有持久性 。 在BPlusTree类中,我们已经为您实现了一个名为UpdateRootPageId的函数。 您需要做的就是在B + Tree索引的root_page_id更改时调用此函数。
您的B + Tree实现必须隐藏key/value等的详细信息,建议使用如下结构:
这些类别已经为你实现了
KeyType: The type of each key in the index. This will only beGenericKey, the actual size ofGenericKeyis specified and instantiated with a template argument and depends on the data type of indexed attribute.ValueType: The type of each value in the index. This will only be 64-bit RID.KeyComparator: The class used to compare whether twoKeyTypeinstances are less/greater-than each other. These will be included in theKeyTypeimplementation files.
你必须使用传入的transaction,把已经加锁的页面保存起来。
我们提供了读写锁存器的实现(
src / include / common / rwlatch.h)。 并且已经在页面头文件下添加了辅助函数来获取和释放Latch锁(src / include / storage / page / page.h)。
首先附上书上的b+树插入算法

对上面几种情况的分析
1. 如果当前为空树则创建一个叶子结点并且也是根节点
这里是
leaf结点所以这里需要用到leaf page内的函数注意这里需要用lab1实现的buffer池管理器来获得page。 这里记得创建完新的结点之后要unpin
进行插入的时候用二分插入来进行优化
1. 创建新结点
2. insert函数#
这里的insert函数可以直接用之前的KeyIndex函数
2. 否则寻找到插入元素应该在的叶子结点,并插入(不分裂)
首先找到叶子结点
如果叶子结点内的元素个数小于最大值则直接插入
否则需要进行分裂。产生两个新的结点。把元素上提
如果提到父亲结点,父结点仍需要分裂。则递归进行分裂否则结束
如果叶子结点内的关键字小于m-1,则直接插入到叶子结点
1. LookUp函数实现
Lookup函数用来寻找包含输入"key"的children pointer(其实就是page_id)
2. findLeafPage#
由于要找到应该插入的LeafPage所以这个函数狠狠重要。但是这里是非并发下插入,在这里用findLeafPage进行对插入算法的测试。后面对于并发情况会有所修改。
从整个b+树的根节点开始。一直向下找到叶子结点
因为b+树是多路搜索树,所以整个向下搜索就是通过key值进行比较
其中内部结点向下搜索的过程利用了上面提到的
lookup函数
3. 无分裂直接插入
3. 分裂的情况
InsertLeaf主函数接上文。
1. 调用split函数对叶子结点进行分割
split的时候会产生一个含有m-m/2个关键字的新结点。注意把两个叶子结点连接起来。
这里注意split函数要区分叶子结点和内部结点。因为叶子结点需要更新双向链表
这里涉及到了MoveHalfTo函数简单的附上一下,这个非常简单
2. InsertIntoParent函数实现
这个函数的实现先看一下书上给出的算法

如果
old_node就是根节点,那么就要创建一个新的节点R当作根节点。然后取key的值当作根节点的值。修改old_node和new_node的父指针。以及根节点的孩子指针
找到分裂的叶子结点的父亲节点随后进行判断
a. 如果可以直接插入则直接插入
b. 否则需要对父结点在进行分裂,即递归调用。
好了第一部分的测试就通过了

附上一个pass的截图完成第一部分✅ 如果我们插入1、2、3、4、5那么我们用程序得到的结果如下:

可以发现是完全正确的 🌟
3. ⚠️一些细节
1. 关于内部结点和叶子结点的区别
1.1 大小不一样
内部结点的最大结点个数是比叶子结点多一
例如m = 3, 那么内部结点的个数就可以是3。而叶子结点则最多是2,但是内部结点的array[0]实际上就是个存地址的。它的key在我们的Draw结果图中都不显示。
1.2 在Split的时候有区别
在叶子结点split的时候需要进行双向链表的维护
而在内部结点则不需要
共有操作都是获得一个新页--> 类型转换 ---> MoveHalfTo
2. upin的pin的注意事项
当你利用
FetchPage拿到一个page的时候他就是pined当你使用完之后记得要
unpin这很重要
3. debug的一些小技巧
利用可视化网站和代码中给的
b_plus_print_test这个测试,把输入图打印成xxx.dot然后复制里面的内容在http://dreampuf.github.io/GraphvizOnline/显示进行对比。对于
Mac系统利用Clion可以直接对测试文件debug。还是非常爽的。其中lldb的利用非常重要。
4. maxSize的含义
这里要注意在进行B+树初始化时候给的
internal_max_size可以认为指的是指针数。也就是说假设我们有m = 3的b+树
inter_max_size = 3 是可以有三个key。但是 leaf_max_size = 2 就只能包含一个key。这个在测试用例被卡才发现的。
所以maxSize()函数可以这样实现
Last updated