CMU-DB 2022 Project 2

CMU-DB 2022 Project 2_第1张图片

第一个Task是实现B+树节点的。B+树节点分为两种,一种是leaf节点,一个是 internal节点

这两个节点都是继承于一个基类-------包含了节点共用的信息。我认为在设计上可以不把操作封装在节点内部。 可以在b_plus_tree分装一些函数。这样可能方便一点, 应为comparator_, buffer, root_page_id都在b_plus_tree,这样可以少传些参数。当然一些后移前移,为插入节点空出位置之类的的操作可以适当封装在节点内部。这一块没什么好讲的

CMU-DB 2022 Project 2_第2张图片

第二个Task是实现B+树结构的

B+树的每一个节点都是一个页面,这就用到的Project1写的buffer_pool_manager,注意一定要确保每个页用完都unpin了才行,不然后面buffer_pool_manager里面的LRU-k-replacer满了后,fetch_page拿到了就是NULL, 用了后,就会发生段错误。下面是体现以上说明的一个函数。

void ChangeMergeParent(page_id_t page_id, page_id_t parent_id) {
    auto page = buffer_pool_manager_->FetchPage(page_id);
    auto tree_page = reinterpret_cast(page->GetData());
    tree_page->SetParentPageId(parent_id);
    buffer_pool_manager_->UnpinPage(page_id, true);
    return;
  }

GetValue函数

寻找key时,internal中第一个key是废弃的。这里二分要注意这个点。如果key < 2th key 的话, 就是要去第一个点。

可以考虑分装一个函数来找到要找到leaf_page,这个是三个函数都能用到的,最好在关键地方打上log,就比如说这个leaf_page的id,方便debug

auto FindKey(const KeyType &key) const -> Page * {
    if (IsEmpty()) {
      return nullptr;
    }
    auto page = buffer_pool_manager_->FetchPage(root_page_id_);
    LOG_INFO("find start from root page is %d", root_page_id_);
    if (reinterpret_cast(page->GetData())->IsLeafPage()) {
      return page;
    }
    buffer_pool_manager_->UnpinPage(root_page_id_, false);
    return DownSearchLeafPage(root_page_id_, key);
  }

然后这个函数也没什么说的了。

Insert函数

这就用到了第一个函数里说的FindKey,找到LeafPage后, 二分出插入点,后移,插入。如果到达max_size的话,就要分裂,newPage, 然后分开叶子节点的k / v 。调整next_page_id,节点值等等。然后插入到internal_page上,如果internal_page也满了的话,就要分开。注意把internal_page的child的parent_page_id也要设置。如果插入到了一个Page_id = -1也就是INVAL_PAGE_ID的话,也就是根节点分裂了。updateRoot,在page0更新root_id,也就是数据库元数据,到这,这个函数的要点也讲完了。

INDEX_TEMPLATE_ARGUMENTS
auto BPLUSTREE_TYPE::Insert(const KeyType &key, const ValueType &value, Transaction *transaction) -> bool {
  mutex_.lock();
  LOG_INFO("--------------------------");
  LOG_INFO("insert value key is %ld", key.ToString());
  LOG_INFO("--------------------------");
  if (IsEmpty()) {
    LOG_INFO("get new root");
    GetNewRoot(key, value);
    mutex_.unlock();
    return true;
  }
  auto page = DownSearchLeafPage(root_page_id_, key);
  if (page == nullptr) {
    mutex_.unlock();
    return false;
  }
  auto leaf_page = reinterpret_cast(page->GetData());
  KeyType insert_key;
  if (!leaf_page->SetKeyAndValue(key, value, comparator_)) {
    LOG_INFO("insert fail");
    buffer_pool_manager_->UnpinPage(leaf_page->GetPageId(), true);
    mutex_.unlock();
    return false;
  }
  // to do unpin page
  LOG_INFO("insert succeed");
  if (leaf_page->GetSize() == leaf_page->GetMaxSize()) {
    page_id_t page_id = 0;
    auto page1 = buffer_pool_manager_->NewPage(&page_id);
    LOG_INFO("start to spilt leaf page, the new page is %d", page_id);
    if (page1 == nullptr) {
      printf("memory out\n");
      mutex_.unlock();
      return false;
    }
    auto next_page = reinterpret_cast(page1->GetData());
    next_page->Init(page_id, leaf_page->GetParentPageId(), leaf_page->GetMaxSize());
    leaf_page->Split(key, value, insert_key, next_page, comparator_);
    auto parent_id = leaf_page->GetParentPageId();
    InternalPage *internal_page = nullptr;
    LOG_INFO("insert split phase 1 succeed");
    if (parent_id == INVALID_PAGE_ID) {
      LOG_INFO("insert change the root");
      page_id_t new_page_id = 0;
      auto new_page = buffer_pool_manager_->NewPage(&new_page_id);
      if (new_page == nullptr) {
        printf("memory out\n");
        mutex_.unlock();
        return false;
      }
      // root information change to do
      auto root_page = reinterpret_cast(new_page->GetData());
      root_page->Init(new_page_id, INVALID_PAGE_ID, internal_max_size_);
      root_page->AfterInitSet(leaf_page->GetPageId());
      leaf_page->SetParentPageId(new_page_id);
      next_page->SetParentPageId(new_page_id);
      root_page_id_ = new_page_id;
      UpdateRootPageId(1);
      internal_page = root_page;
    } else {
      auto parent_page = buffer_pool_manager_->FetchPage(parent_id);
      if (parent_page == nullptr) {
        LOG_ERROR("no memory");
        abort();
      }
      internal_page = reinterpret_cast(parent_page->GetData());
    }
    LOG_INFO("insert split phase 2 start");
    buffer_pool_manager_->UnpinPage(page_id, true);
    LOG_INFO("point1");
    if (!AfterKeyAndSetValue(internal_page, insert_key, page_id)) {
      LOG_INFO("point2");
      buffer_pool_manager_->UnpinPage(internal_page->GetPageId(), true);
      buffer_pool_manager_->UnpinPage(leaf_page->GetPageId(), true);
      mutex_.unlock();
      return false;
    }
    LOG_INFO("insert split phase 2 succeed");
    buffer_pool_manager_->UnpinPage(internal_page->GetPageId(), true);
  }
  buffer_pool_manager_->UnpinPage(leaf_page->GetPageId(), true);
  LOG_INFO("all insert succeed");
  mutex_.unlock();
  return true;
}

Remove函数

bool LeafBinarySearchAndRemove(LeafPage *page, const KeyType &key, int *flag) {
    int size = page->GetSize();
    int l = 0, r = size - 1;
    while (l < r) {
      int mid = (l + r) >> 1;
      if (comparator_(page->KeyAt(mid), key) >= 0) {
        r = mid;
      } else {
        l = mid + 1;
      }
    }
    if (comparator_(key, page->KeyAt(l)) != 0) {
      return false;
    }
    LOG_INFO("%d %d %d", l, page->GetSize(), page->GetMinSize());
    // to do
    // I think this can put the tree part ? because the interface is public?
    // else I will do more thing!
    auto array = page->GetArray();
    // case 1
    if (l == 0 && page->GetSize() > 1) {
      LeafParentChangeKey(page, page->KeyAt(1), page->KeyAt(l));
    } else if (l == page->GetSize() - 1 && page->GetSize() > 1) {
      LeafParentChangeKey(page, page->KeyAt(page->GetSize() - 2), page->KeyAt(l));
    }
    if (size > page->GetMinSize() || page->GetPageId() == root_page_id_) {
      // to do == max or == min
      for (int i = l + 1; i < size; i++) {
        array[i - 1] = array[i];
      }
      page->IncreaseSize(-1);
      if (page->GetSize() == 0) {
        root_page_id_ = INVALID_PAGE_ID;
        LOG_INFO("root invail !!!!!!");
        // UpdateRootPageId(1);
        *flag = 1;
      }
      return true;
    }
    if (size < page->GetMinSize()) {
      // root is leaf to do
      for (int i = l + 1; i < size; i++) {
        array[i - 1] = array[i];
      }
      page->IncreaseSize(-1);
      return true;
    }
    if (size == page->GetMinSize()) {
      for (int i = l + 1; i < size; i++) {
        array[i - 1] = array[i];
      }
      LOG_INFO("this postion");
      page->IncreaseSize(-1);
      if (LeafFindLeftPage(page, key)) {
        LOG_INFO("case 1");
        return true;
      }
      if (LeafFindRightPage(page, key)) {
        LOG_INFO("case 2");
        return true;
      }
      if (LeafMergeLeft(page, key)) {
        *flag = 1;
        LOG_INFO("case 3");
        return true;
      }
      if (LeafMergeRight(page, key)) {
        LOG_INFO("case 4");
        // *flag = 1;
        return true;
      }
      LOG_INFO("end");
      // to do
    }
    // case 2
    return false;
  }

这个函数大体描述了删除的过程。删除后如果到达最小size的话,就有4种选择。补一个,or 合并

往左往右找。一个细节merge时要左边merge右边的,这样nextPageId就可以正常。因为这leaf_page本质上就是一个单向链表。还有就是节点的最大key和最小key变的话,还要更新一下internal的key值。细节就不多说了,要向上递归的删除,改parent_page_id等等

CMU-DB 2022 Project 2_第3张图片

Task 3就是想单链表一样的迭代器。但是构造要把这个buffer 和comparator 传进去,因为是分page的

Task 4 就加了一把大锁。。。因为刚开始不知道task4要transacion记录和page要加锁,设计的很杂乱。还是要想清楚再写。不然就是堆屎山。。。还有不要图方便把实现写到头文件里去。这样编译就要重新编译很多(to do)。多打LOG,分级LOG。二分学到了挺多的orz

参考文章:做个数据库:2022 CMU15-445 Project2 B+Tree Index - 知乎

                  B+树看这一篇就够了(B+树查找、插入、删除全上) - 知乎

你可能感兴趣的:(CMU,15-445学习,mysql,数据库)