CMU數據庫(15-445)-實驗2-B+樹索引實現(中)刪除


3. Delete 實現

附上實驗2的第一部分🔗 https://www.cnblogs.com/JayL-zxl/p/14324297.html
附上實驗2的第三部分🔗 https://www.cnblogs.com/JayL-zxl/p/14332249.html

1. 刪除算法原理

cmu這里給了演示網站 https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

關於整個刪除算法的講解這個ppthttp://courses.cms.caltech.edu/cs122/lectures-wi2018/CS122Lec11.pdf講的比較清楚

算法描述見下表

image-20210121112759338

以及下圖


2 刪除算法實現

2.1 第一步 : 找到包含目標key的leaf node進行刪除

  1. 找到包含目標key的leaf node

  2. 如果當前是空樹則立即返回

  3. 否則先找到要刪除的key所在的page

  4. 隨后調用RemoveAndDeleteRecord在葉page上直接刪除key值

INDEX_TEMPLATE_ARGUMENTS
void BPLUSTREE_TYPE::Remove(const KeyType &key, Transaction *transaction) {
  {
    // 這個專門針對於非並發的delete
    Page *page = FindLeafPage(key, false);
    LeafPage *leafPage = reinterpret_cast<LeafPage*>(page->GetData()); //pined leafPage

    leafPage->RemoveAndDeleteRecord(key,comparator_);

  1. RemoveAndDeleteRecord函數

  2. 這里實現越來越像Leveldb是怎么回事。。

  3. 利用KeyIndex函數實現真的簡單。

INDEX_TEMPLATE_ARGUMENTS
int B_PLUS_TREE_LEAF_PAGE_TYPE::RemoveAndDeleteRecord(const KeyType &key, const KeyComparator &comparator) {
  int pos = KeyIndex(key, comparator);
  if (pos < GetSize() && comparator(array_[pos].first, key) == 0) {
    for (int j = pos + 1; j < GetSize(); j++) {
      array_[j - 1] = array_[j];
    }
    IncreaseSize(-1);
  }
  return GetSize();
}

之后就是對於刪除的處理,主要有兩個一個是合並,一個就是redistribute。具體流程見下


2.2 Coalesce實現流程

葉子結點內關鍵字個數小於最小值向下執行。調用刪除的核心函數CoalesceAndRedistribute


下面是CoalesceAndRedistribute的邏輯

1.如果當前結點是根節點則調用AdjustRoot(node)

這里的提示給了其實這個函數就針對兩種情況

  • Case1 : old_root_node是內部結點,且大小為1,表示內部結點其實已經沒有key了。所以要把它的孩子更新成新的根節點
  • Case2 : old_root_node是葉子結點。且大小為0,直接刪了就好。

否則不需要有page被刪除,則直接return flase

INDEX_TEMPLATE_ARGUMENTS
bool BPLUSTREE_TYPE::AdjustRoot(BPlusTreePage *old_root_node) {
  // case 1 old_root_node (internal node) has only one size
  if (!old_root_node->IsLeafPage() && old_root_node->GetSize() == 1) {
    InternalPage *old_root_page = reinterpret_cast<InternalPage *>(old_root_node);
    page_id_t new_root_page_id = old_root_page->adjustRootForInternal();
    root_page_id_ = new_root_page_id;
    UpdateRootPageId(0);

    Page *new_root_page = buffer_pool_manager_->FetchPage(new_root_page_id);
    BPlusTreePage *new_root = reinterpret_cast<BPlusTreePage *>(new_root_page->GetData());
    new_root->SetParentPageId(INVALID_PAGE_ID);
    buffer_pool_manager_->UnpinPage(new_root_page_id, true);

    return true;
  }
  // case 2 : all elements deleted from the B+ tree
  if (old_root_node->IsLeafPage() && old_root_node->GetSize() == 0) {
    root_page_id_ = INVALID_PAGE_ID;
    UpdateRootPageId(0);
    return true;
  }
  return false;
}

2.否則先判斷是否要進行Coalesce

這里要找兄弟結點進行合並,如果滿足合並要求的話

1. 判斷是否滿足合並要求

這里利用一個輔助函數進行判斷。如果不超過最大size就可以合並

INDEX_TEMPLATE_ARGUMENTS
template <typename N>
bool BPLUSTREE_TYPE::IsCoalesce(N *nodeL, N *nodeR) {
  // maxSize consider InternalPage and LeafPage
  return nodeL->GetSize() + nodeR->GetSize() <= maxSize(nodeL);
}

⚠️ 合並函數是和直接前驅進行合並,也就是和它左邊的node進行合並

2. 判斷左邊的page是否能進行合並
  // firstly find from left
    if (left_sib_index >= 0) {
      page_id_t sibling_pid = parent->ValueAt(left_sib_index);
      Page *sibling_page = buffer_pool_manager_->FetchPage(sibling_pid);  // pined sibling_page
      N *sib_node = reinterpret_cast<N *>(sibling_page->GetData());
      if (IsCoalesce(node, sib_node)) {
        // coalesce
        // unpin node and deleted node
        bool del_parent = Coalesce(&sib_node, &node, &parent, cur_index, transaction);
         // unpin sibling page
        buffer_pool_manager_->UnpinPage(sibling_pid, true);     
        // unpin parent page;
        buffer_pool_manager_->UnpinPage(parent_pid, true);                             
        if (del_parent) {
          buffer_pool_manager_->DeletePage(parent_pid);
        }
        return false;  // node is merged into sib, and already deleted
      }
      buffer_pool_manager_->UnpinPage(sibling_pid, false);  // unpin sibling page
    }
3. 否則判斷右邊是否能進行合並
// secondly find from right

    if (right_sib_index < parent->GetSize()) {
      page_id_t sibling_pid = parent->ValueAt(right_sib_index);
      Page *sibling_page = buffer_pool_manager_->FetchPage(sibling_pid);  // pined sibling_page
      N *sib_node = reinterpret_cast<N *>(sibling_page->GetData());
      if (IsCoalesce(node, sib_node)) {
        // coalesce
        // unpin right sib and deleted right sib
        bool del_parent =
            Coalesce(&node, &sib_node, &parent, cur_index, transaction);  
        buffer_pool_manager_->UnpinPage(node->GetPageId(), true);         // unpin sibling page
        buffer_pool_manager_->UnpinPage(parent_pid, true);                // unpin parent page;
        if (del_parent) {
          buffer_pool_manager_->DeletePage(parent_pid);
        }
        return false;  // node is merged into sib, and already deleted
      }
      buffer_pool_manager_->UnpinPage(sibling_pid, false);  // unpin sibling page
    }
4. Coalece函數的實現

實現之前先看兩張圖

在合並之后,父親結點必須要更新。因為移動操作導致了之前父結點的指針發生了錯誤。這里會涉及到父親結點是否需要刪除的情況

具體情況見下圖

可能由於葉子結點的合並操作,導致父親結點變成null空結點。或者說是其不滿足最小結點個數要求。這樣就要對父親結點進行處理,這個時候是父親結點也可以進行合並,這個時候原來的父親結點就無了。合並之后的結果如下圖

記得遞歸處理就好

INDEX_TEMPLATE_ARGUMENTS
template <typename N>
bool BPLUSTREE_TYPE::Coalesce(N **neighbor_node, N **node,
                              BPlusTreeInternalPage<KeyType, page_id_t, KeyComparator> **parent, int index,
                              Transaction *transaction) {
  // Assume that *neighbor_node is the left sibling of *node

  // Move entries from node to neighbor_node
  if ((*node)->IsLeafPage()) {
    LeafPage *leaf_node = reinterpret_cast<LeafPage *>(*node);
    LeafPage *leaf_neighbor = reinterpret_cast<LeafPage *>(*neighbor_node);

    leaf_node->MoveAllTo(leaf_neighbor);
    leaf_neighbor->SetNextPageId(leaf_node->GetNextPageId());
  } else {
    InternalPage *internal_node = reinterpret_cast<InternalPage *>(*node);
    InternalPage *internal_neighbor = reinterpret_cast<InternalPage *>(*neighbor_node);

    internal_node->MoveAllTo(internal_neighbor, (*parent)->KeyAt(index), buffer_pool_manager_);
  }

  buffer_pool_manager_->UnpinPage((*node)->GetPageId(), true);
  buffer_pool_manager_->DeletePage((*node)->GetPageId());

  (*parent)->Remove(index);

  // If parent is underfull, recursive operation
  if ((*parent)->GetSize() < minSize(*parent)) {
    return CoalesceOrRedistribute(*parent, transaction);
  }
  return false;
}
5. 葉子結點和內部結點對應的兩個MoveAllTo函數

這個函數就不寫了。實現比較簡單,唯一要注意的就是對於內部結點的合並操作,要把需要刪除的內部結點的葉子結點轉移過去。也就是要有下面這樣的一行

recipient->array_[recipient->GetSize()].first = middle_key;

3. Redistribute流程

當然也可以先看一下算法示意圖。

下圖是對葉子結點的的Redistribute函數

這里在移動的時候只要記得更新父親對應index的key值就好了。

然而對於內部結點則並不是這么簡單的情況了。內部結點可以直接從它的兄弟結點copy然后修改其根節點嗎,這顯然不合理。

對於這種情況的處理可以見下圖




因此整個redistribute所涉及的四種情況就如下

  1. 向葉子結點左邊借
  2. 向葉子結點右邊借
  3. 內部結點左邊借
  4. 內部結點右邊借

1. 對於葉子結點向左邊借的情況

好了刪除算法已經實現了。首先我們可以通過test函數

cd build
make b_plus_tree_delete_test
./test/b_plus_tree_delete_test --gtest_also_run_disabled_tests
image-20210126134731557

然后我們自己做一些test。這里我就拿一個例子來看

插入10、5、7、4、9得到下圖是正確的🌟

image-20210126134908455

然后刪除元素7

image-20210126134952965

可以發現是完全正確的好了。第二部分就完成了。下面就是最后一部分對於🔒的實現和迭代器的實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM