CMU15445 FALL2022 Project #2 - B+Tree

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
    • 删除
      • 将root page加写锁
      • 循环查找时
        • 锁住当前page,之前的page进行AddIntoPageSet到transaction
        • 根据条件判断是否可以ReleaseLatchFromQueue
          • size>minsize
  • 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可能页面已经被换出

最后放个线上测试图
在这里插入图片描述
在这里插入图片描述

  • 55
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值