第一个Task是实现B+树节点的。B+树节点分为两种,一种是leaf节点,一个是 internal节点
这两个节点都是继承于一个基类-------包含了节点共用的信息。我认为在设计上可以不把操作封装在节点内部。 可以在b_plus_tree分装一些函数。这样可能方便一点, 应为comparator_, buffer, root_page_id都在b_plus_tree,这样可以少传些参数。当然一些后移前移,为插入节点空出位置之类的的操作可以适当封装在节点内部。这一块没什么好讲的
第二个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等等
Task 3就是想单链表一样的迭代器。但是构造要把这个buffer 和comparator 传进去,因为是分page的
Task 4 就加了一把大锁。。。因为刚开始不知道task4要transacion记录和page要加锁,设计的很杂乱。还是要想清楚再写。不然就是堆屎山。。。还有不要图方便把实现写到头文件里去。这样编译就要重新编译很多(to do)。多打LOG,分级LOG。二分学到了挺多的orz
参考文章:做个数据库:2022 CMU15-445 Project2 B+Tree Index - 知乎
B+树看这一篇就够了(B+树查找、插入、删除全上) - 知乎