CMU15445 FALL2022 Project #2 - B+Tree
Task #1 - B+Tree Pages
- 填充b_plus_tree_page.cpp相关Get、Set方法
b_plus_tree_internal_page
-
存储中间节点,最左边的中间节点的第一个key为空(这里我每一层只有第一个内部节点的第一个key为空)
-
auto KeyAt(int index) const -> KeyType; // 根据index,返回对应key void SetKeyAt(int index, const KeyType &key); // 根据index,设置对应key auto ValueAt(int index) const -> ValueType; // 根据index,返回对应value void SetValueAt(int index, const ValueType &value); // 根据index,设置对应value auto ValueIndex(const ValueType &value) -> int; // 根据value,查找index void InsertNodeAfter(const KeyType &key, const ValueType &value); // 在头部插入kv void InsertNodeBefore(const KeyType &key, const ValueType &value); // 在尾部插入kv auto Lookup(const KeyType &key, const KeyComparator &KeyCmp, bool 。leftmost, bool rightmost) -> page_id_t; // 根据传入key、KeyCmp,利用二分查找搜索对应pageid,leftmost返回最左边pageid,rightmost为true返回最后边pageid auto Insert(const KeyType &key, const ValueType &value, const KeyComparator &KeyCmp) -> bool; // 按序将kv插入 void Remove(int index); // 删除index对应内容 auto RemoveAndReturnOnlyChild() -> page_id_t; // assert(GetSize() == 1),返回唯一的pageid,用于调整根节点 void MoveFirstToEndOf(BPlusTreeInternalPage *recipient, BufferPoolManager *buffer_pool_manager_); // 将第一个节点kv转移到recipient的尾部 void CopyLastFrom(const MappingType &pair, BufferPoolManager *buffer_pool_manager_); // 将节点pair增加到尾部并更新子节点 void MoveLastToFrontOf(BPlusTreeInternalPage *recipient, BufferPoolManager *buffer_pool_manager_); // 将最后一个节点kv转移到recipient的头部 void CopyFirstFrom(const MappingType &pair, BufferPoolManager *buffer_pool_manager_); // 将节点pair增加到头部并更新子节点
b_plus_tree_leaf_page
-
存储叶子节点
-
auto GetNextPageId() const -> page_id_t; // 根据下一个索引的pageid void SetNextPageId(page_id_t next_page_id); // 设置下一个索引的pageid auto KeyAt(int index) const -> KeyType; // 根据index,返回对应key auto ValueAt(int index) const -> ValueType; // 根据index,返回对应value auto KeyIndex(const KeyType &key, const KeyComparator &KeyCmp) -> int; // 根据key,查找index void SetValueAt(int index, const ValueType &value); // 根据index,设置对应value void InsertNodeAfter(const KeyType &key, const ValueType &value); // 在头部插入kv void InsertNodeBefore(const KeyType &key, const ValueType &value); // 在尾部插入kv auto GetItem(int index) -> MappingType &; auto Lookup(const KeyType &key, ValueType *value, const KeyComparator &KeyCmp) const -> bool; // 根据传入key、KeyCmp,利用二分查找搜索对应pageid auto Insert(const KeyType &key, const ValueType &value, const KeyComparator &KeyCmp) -> bool; // 按序将kv插入 auto RemoveAndDeleteRecord(const KeyType &key, const KeyComparator &KeyCmp) -> bool; // 根据key,删除对应节点 void MoveFirstToEndOf(BPlusTreeLeafPage *recipient, BufferPoolManager *buffer_pool_manager_); // 将第一个节点kv转移到recipient的尾部 void MoveLastToFrontOf(BPlusTreeLeafPage *recipient, BufferPoolManager *buffer_pool_manager_); // 将最后一个节点kv转移到recipient的头部
Task #2 - B+Tree Data Structure
- 实现B+Tree Data Structure,主要有GetValue、Insert和Remove
FindLeaf
- 根据传入的key,循环查找叶子节点
- 首先将pageid设置为根节点pageid,并FetchPage,转换为BPlusTreePage
- 如果是叶子节点,返回page
- 否则UnpinPage并转换为InternalPage
- 根据key Lookup查找往下查找子节点,并更新pageid,返回上面继续搜寻
GetValue
- 如果为空,返回false
- 根据key通过FindLeaf查找包含给定键值的叶子节点
- 在叶子节点中查找键值
- 如果找到,将value加入result
- UnpinPage并返回true
Insert
- 如果为空
- 初始化根节点,NewPage并转为LeafPage,进行Init
- 将kv插入,UnpinPage并返回true
- FindLeaf找到插入位置
- 在叶子节点中插入键值
- 如果已存在该键值,UnpinPage返回false
- 如果成功插入且没满(注意最好满的时候就进行分裂),UnpinPage返回true
- 叶子节点已满,分裂叶子节点
- 创建一个新的叶子节点
- 计算分裂点位置
- 将原叶子节点的后半部分移动到新的叶子节点中
- 修改叶子节点的ParentPageId
- 更新原叶子节点的大小和nextpage指针
- 递归更新父节点
- 如果父节点为空,说明左子节点是根节点
- 创建一个新的根节点
- 将新节点设置为根节点,并更新相应子节点、父节点
- 插入新的键值对到父节点中
- 如果成功插入且没满,return
- 如果父节点已满,需要进行分裂
- 创建一个新的内部节点
- 计算分裂点位置
- 将原内部节点的后半部分移动到新的内部节点中
- 更新原内部节点的大小
- 继续递归更新父节点
- 如果父节点为空,说明左子节点是根节点
Remove
- 如果为空直接返回
- FindLeaf找到要删除的叶子节点
- 从叶子节点中删除键值对
- 如果删除后叶子节点的元素数目少于最小允许元素数目,进行合并或借位
- 如果是根节点
- 如果是叶子节点,且size为0,说明空了,将root_page_id_ 设置为 INVALID_PAGE_ID
- 否则为内容节点且size为1时,将根节点替换为唯一的子节点
- 先寻找左边的兄弟节点,如果index = 0则找右边
- 如果当前兄弟size大于minsize,先进行借位
- 向兄弟节点进行一个节点,注意更新子节点ParentPageId
- 否则如果当前size和兄弟size和小于maxsize,进行合并
- 先将节点的内容合并到邻居节点中
- 更新父节点,删除对应索引
- 如果父节点size小于minsize,递归进行借位或合并
- 如果是根节点
遇到的奇怪bug
-
每个Fetch或者Newpage,都要对应UnpinPage
-
Insert时插入超过了maxsize再进行分裂,当maxsize默认时会造成越界,需要先分裂再插入,所以最好是=maxsize就分裂
-
分裂中间节点时要注意修改子节点的ParentPageId
-
Delete结尾或者合并时,注意更新NextPageId和子节点的ParentPageId
Task #3 - Index Iterator
-
叶子节点有nextpageid属性,可以进行顺序索引
-
重载==()、*()、++()、!=(),主要是++
-
auto INDEXITERATOR_TYPE::operator++() -> INDEXITERATOR_TYPE & { index_++; if (index_ >= curr_page_->GetSize() && curr_page_->GetNextPageId() != INVALID_PAGE_ID) { page_id_t next_pid = curr_page_->GetNextPageId(); Page *next_page = bpm_->FetchPage(next_pid); auto *next_node = reinterpret_cast<LeafPage *>(next_page->GetData()); curr_page_ = next_node; bpm_->UnpinPage(next_page->GetPageId(), false); index_ = 0; } return *this; }
-
Begin()返回叶子节点的第一个Index Iterator
- 大体同FindLeaf,Lookup时将leftmost设置为true
-
End()返回叶子节点的最后一个Index Iterator
- 大体同FindLeaf,Lookup时将rightmost设置为true
Task #4 - Concurrent Index
-
主要要处理Findleaf、GetValue、Insert和Remove
-
每次处理root_page也要加锁,
-
对于插入删除,利用transaction统一管理,
-
root_page_id_latch_
通过插入null到transaction来进行管理 -
void BPLUSTREE_TYPE::ReleaseLatchFromQueue(Transaction *transaction) { while (!transaction->GetPageSet()->empty()) { Page *page = transaction->GetPageSet()->front(); transaction->GetPageSet()->pop_front(); if (page == nullptr) { root_page_id_latch_.WUnlock(); } else { page->WUnlatch(); buffer_pool_manager_->UnpinPage(page->GetPageId(), false); } } }
-
-
FindLeaf
- 查找
- 先释放根锁,并读锁住
root page
- 循环查找时,释放之前的page,锁住当前page
- 先释放根锁,并读锁住
- 插入
- 将root page加写锁
- 循环查找时
- 锁住当前page,之前的page进行
AddIntoPageSet
到transaction - 根据条件判断是否可以
ReleaseLatchFromQueue
- 叶子节点size<maxsize-1
- 中间节点size<maxsize
- 锁住当前page,之前的page进行
- 删除
- 将root page加写锁
- 循环查找时
- 锁住当前page,之前的page进行
AddIntoPageSet
到transaction - 根据条件判断是否可以
ReleaseLatchFromQueue
- size>minsize
- 锁住当前page,之前的page进行
- 查找
-
GetValue
- 先
root_page_id_latch_.RLock
- Findleaf
- 最后只需要释放leaf_page->RUnlatch()
- 先
-
Insert
- 先root_page_id_latch_.WLock,并标记为null加入transaction
- Findleaf
- 最后只需要ReleaseLatchFromQueue(transaction)并释放leaf_page->WUnlatch()
-
Delete
- 先
root_page_id_latch_.WLock
,并标记为null
加入transaction
- Findleaf
- 如果需要借位或合并需要锁住兄弟节点
- 最后只需要
ReleaseLatchFromQueue(transaction)
并释放leaf_page->WUnlatch()
- 先
-
Begin和End
- 前面同GetValue
- 在IndexIterator析构函数进行
page_->RUnlatch()
并UnpinPage - ++时要读锁住nextpage,并且释放当前page读锁
-
注意需要先解锁在进行UnpinPage,因为如果先Uppin可能页面已经被换出
最后放个线上测试图