Lab2

在做实验2之前请确保实验1结果的正确性。不然你的实验2将无法正常进行

环境搭建地址如下 https://www.cnblogs.com/JayL-zxl/p/14307260.html

实验一的地址如下 https://www.cnblogs.com/JayL-zxl/p/14311883.html

实验的地址如下 https://15445.courses.cs.cmu.edu/fall2020/project2/

0. 写在前面

Lab2真的好难写啊。写了好几天(虽然中间有回家、做核酸、出去玩。。各种的事情)但还算是写完了。真的参考了好多代码(这里建议大家有问题还是Google),最后勉强写完了真的不容易,下面记录一下我实验的过程。(写的超烂)

1. 实验介绍

第一个打分点---实现b+树的基本结构、插入、搜索操作

注意这里没有考虑打分点2的并发问题,所以对于加锁、解锁和事物都没有考虑。

第二个打分点--实现b+树的删除操作、索引迭代器和对并发访问的支持

Task 1 B+TREE PAGES

您需要实现三个页面类来存储B+树的数据。

  • B+ Tree Parent Page
  • B+ Tree Internal Page
  • B+ Tree Leaf Page
1. B+ Tree Parent Page

这是内部页和叶页都继承的父类,它只包含两个子类共享的信息。父页面被划分为如下表所示的几个字段。

B+Tree Parent Page Content

Variable Name Size Description
page_type_ 4 Page Type (internal or leaf)
lsn_ 4 Log sequence number (Used in Project 4)
size_ 4 Number of Key & Value pairs in page
max_size_ 4 Max number of Key & Value pairs in page
parent_page_id_ 4 Parent Page Id
page_id_ 4 Self Page Id

您必须在指定的文件中实现您的父页。您只能修改头文件(src/include/storage/page/b_plus_tree_page.h) 和其对应的源文件 (src/storage/page/b_plus_tree_page.cpp).

2. B+TREE INTERNAL PAGE

内部页不存储任何实际数据,而是存储有序的m个键条目和m + 1个指针(也称为page_id)。 由于指针的数量不等于键的数量,因此将第一个键设置为无效,并且查找方法应始终从第二个键开始。 任何时候,每个内部页面至少有一半已满。 在删除期间,可以将两个半满页面合并为合法页面,或者可以将其重新分配以避免合并,而在插入期间,可以将一个完整页面分为两部分。

你只能修改头文件(src/include/storage/page/b_plus_tree_internal_page.h) 和对应的源文件(src/page/b_plus_tree_internal_page.cpp).

* Internal page format (keys are stored in increasing order):
* --------------------------------------------------------------------------
* | HEADER | KEY(1)+PAGE_ID(1) | KEY(2)+PAGE_ID(2) | ... | KEY(n)+PAGE_ID(n) |
* --------------------------------------------------------------------------
#define INDEX_TEMPLATE_ARGUMENTS template <typename KeyType, typename ValueType, typename KeyComparat>
  1. B+TREE LEAF PAGE

叶子页存储有序的m个键条目(key)和m个值条目(value)。 在您的实现中,值只能是用于定位实际元组存储位置的64位record_id,请参阅src / include / common / rid.h中定义的RID类。 叶子页与内部页在键/值对的数量上具有相同的限制,并且应该遵循相同的合并,重新分配和拆分操作。您必须在指定的文件中实现内部页。 仅允许您修改头文件(src / include / storage / page / b_plus_tree_leaf_page.h)及其相应的源文件(src / storage / page / b_plus_tree_leaf_page.cpp)。

重要提示:尽管叶子页和内部页包含相同类型的键,但它们可能具有不同类型的值,因此叶子页和内部页的最大大小可能不同。每个B + Tree叶子/内部页面对应从缓冲池获取的存储页面的内容(即data_部分)。 因此,每次尝试读取或写入叶子/内部页面时,都需要首先使用其唯一的page_id从缓冲池中提取页面,然后将其重新解释为叶子或内部页面,并在写入或删除后执行unpin操作。

Task 2.A - B+TREE DATA STRUCTURE (INSERTION & POINT SEARCH)

您的B +树索引只能支持唯一键。 也就是说,当您尝试将具有重复键的键值对插入索引时,它应该返回false

对于checkpoint1,仅需要B + Tree索引支持插入(Insert)和点搜索(GetValue)。 您不需要实现删除操作。 插入后如果当前键/值对的数量等于max_size,则应该正确执行分割。 由于任何写操作都可能导致B + Tree索引中的root_page_id发生更改,因此您有责任更新(src / include / storage / page / header_page.h)中的root_page_id,以确保索引在磁盘上具有持久性 。 在BPlusTree类中,我们已经为您实现了一个名为UpdateRootPageId的函数。 您需要做的就是在B + Tree索引的root_page_id更改时调用此函数。

您的B + Tree实现必须隐藏key/value等的详细信息,建议使用如下结构:

template <typename KeyType,
typename ValueType,
typename KeyComparator>
class BPlusTree{
// ---
};

这些类别已经为你实现了

  • KeyType: The type of each key in the index. This will only be GenericKey, the actual size of GenericKey is specified and instantiated with a template argument and depends on the data type of indexed attribute.

  • ValueType: The type of each value in the index. This will only be 64-bit RID.

  • KeyComparator: The class used to compare whether two KeyType instances are less/greater-than each other. These will be included in the KeyType implementation files.

TASK #2.B - B+TREE DATA STRUCTURE (DELETION)

您的B+树索引需要支持删除。如果删除导致某些页面低于占用阈值,那么您的B+树索引应该正确执行合并或重新分配。同样,您的B+树索引只能支持唯一键

TASK #3 - INDEX ITERATOR

您将构建一个通用索引迭代器,以有效地检索所有叶子页面。 基本思想是将它们组织到一个链接列表中,然后按照B + Tree叶子页中存储的特定方向遍历每个键/值对。 您的索引迭代器应遵循C ++ 17中定义的迭代器功能,包括使用一组运算符对一系列元素进行迭代的能力,以及for-each循环(至少具有++,==,!=和解引用运算符)。 请注意为了支持索引的每个循环功能,您的BPlusTree应该正确实现begin()和end()。

您必须在指定的文件中实现索引迭代器。 仅允许您修改头文件(src / include / storage / index / index_iterator.h)及其相应的源文件(src / index / storage / index_iterator.cpp)。 您不需要修改任何其他文件。 您必须在这些文件中的IndexIterator类中实现以下功能。 在索引迭代器的实现中,只要您具有以下三种方法,就可以添加任何帮助程序方法。

  • isEnd(): Return whether this iterator is pointing at the last key/value pair.
  • operator++(): Move to the next key/value pair.
  • operator*(): Return the key/value pair this iterator is currently pointing at.
  • operator==(): Return whether two iterators are equal
  • operator!=(): Return whether two iterators are not equal.

TASK #4 - CONCURRENT INDEX

在这一部分中,您需要更新原始的单线程B + Tree索引,以便它可以支持并发操作。 我们将使用课堂和教科书中介绍的Latch捕捉技术。 遍历索引的线程将获取然后释放B + Tree页上的Latch锁。 如果线程的子页面被认为是“安全的”,则该线程只能释放其父页面上的Latch锁。 请注意,“安全”的定义可能会根据线程执行的操作类型而有所不同:

  • Search: Starting with root page, grab read (R) latch on child Then release latch on parent as soon as you land on the child page.
  • Insert: Starting with root page, grab write (W) latch on child. Once child is locked, check if it is safe, in this case, not full. If child is safe, release all locks on ancestors.
  • Delete: Starting with root page, grab write (W) latch on child. Once child is locked, check if it is safe, in this case, at least half-full. (NOTE: for root page, we need to check with different standards) If child is safe, release all locks on ancestors.

Hints

  1. 你必须使用传入的transaction,把已经加锁的页面保存起来。
  2. 我们提供了读写锁存器的实现(src / include / common / rwlatch.h)。 并且已经在页面头文件下添加了辅助函数来获取和释放Latch锁(src / include / storage / page / page.h)。

2. Insert实现

首先附上书上的b+树插入算法

对上面几种情况的分析

  1. 如果当前为空树则创建一个叶子结点并且也是根节点

    -- 这里是leaf结点所以这里需要用到leaf page内的函数

    -- 注意这里需要用lab1实现的buffer池管理器来获得page。 这里记得创建完新的结点之后要unpin

    -- 进行插入的时候用二分插入来进行优化

    • 创建新结点
    INDEX_TEMPLATE_ARGUMENTS
    void BPLUSTREE_TYPE::StartNewTree(const KeyType &key, const ValueType &value) {
    auto page = buffer_pool_manager_->NewPage(&root_page_id_);
    if (page == nullptr) {
    throw "all page are pinned";
    }
    auto root =reinterpret_cast<BPlusTreeLeafPage<KeyType, ValueType,KeyComparator> *>(page->GetData());
    UpdateRootPageId(true);
    root->Init(root_page_id_, INVALID_PAGE_ID ,leaf_max_size_);
    root->Insert(key, value, comparator_);
    // unpin
    buffer_pool_manager_->UnpinPage(root->GetPageId(), true);
    }
    • insert函数
    /*
    in b_plus_leaf_page.h
    */
    INDEX_TEMPLATE_ARGUMENTS
    int B_PLUS_TREE_LEAF_PAGE_TYPE::Insert(const KeyType &key, const ValueType &value, const KeyComparator &comparator) {
    if(!GetSize()||comparator(key, KeyAt(GetSize() - 1)) > 0) array[GetSize()] = {key, value};
    else{
    int l=0,r=GetSize()-1;
    while(l<r){
    int mid=(l+r)>>1;
    if(comparator(key,array[mid].first)<0)r=mid;
    else if(comparator(key,array[mid].first)>0)l=mid+1;
    else assert(0);
    }
    memmove(array + r + 1, array + r,static_cast<size_t>((GetSize() - r)*sizeof(MappingType)));
    array[r] = {key, value};
    }
    IncreaseSize(1);
    return GetSize();
    }
  2. 否则寻找插入元素应该在的叶子结点

    a . 如果叶子结点内的关键字小于m-1,则直接插入到叶子结点insert_into_leaf

    • findLeafPage函数有点复杂

    要考虑无论是读或者写从根节点。到叶子结点都需要加锁。然后注意释放锁否则会锁死。(这个地方测试的时候卡死了我好久)

    这里对原来的函数定义做了一些修改。加入了操作类型的判断。

    /*
    定义在b_plus_tree.h中
    定义方法和定义page类型保持一致
    */
    enum class Operation { READ = 0, INSERT, DELETE };
    INDEX_TEMPLATE_ARGUMENTS
    Page *BPlusTree<KeyType, ValueType, KeyComparator>::FindLeafPage(const KeyType &key, bool leftMost, Operation op, Transaction *transaction) { if (IsEmpty()) {
    return nullptr;
    }
    auto root = buffer_pool_manager_->FetchPage(root_page_id_);
    if (root == nullptr) {
    throw "no page can find";
    } if (op == Operation::READ) {
    root->RLatch();
    } else {
    root->WLatch();
    }
    if (transaction != nullptr) {
    transaction->AddIntoPageSet(root);
    } auto node = reinterpret_cast<BPlusTreePage *>(root->GetData());
    while (!node->IsLeafPage()) {
    auto internal =reinterpret_cast<BPlusTreeInternalPage<KeyType, page_id_t,KeyComparator> *>(node);
    page_id_t parent_page_id = node->GetPageId(), child_page_id;
    if (leftMost) {
    child_page_id = internal->ValueAt(0);
    } else {
    child_page_id = internal->Lookup(key, comparator_);
    }
    auto child = buffer_pool_manager_->FetchPage(child_page_id);
    if (child == nullptr) {
    throw "not find child in findLeaf";
    } if (op == Operation::READ) {
    child->RLatch();
    UnlockUnpinPages(op, transaction);
    } else {
    child->WLatch();
    }
    node = reinterpret_cast<BPlusTreePage *>(child->GetData());
    assert(node->GetParentPageId() == parent_page_id);
    // child is locked, If child is safe, release all locks on ancestors.
    if (op != Operation::READ && isSafe(node, op)) {
    UnlockUnpinPages(op, transaction);
    }
    if (transaction != nullptr) {
    transaction->AddIntoPageSet(child);
    } else {
    root->RUnlatch();
    buffer_pool_manager_->UnpinPage(root->GetPageId(), false);
    root = child;
    }
    }
    return reinterpret_cast<Page*>(node);
    }
    • Lookup函数

    找到key值所在的page---二分查找

    INDEX_TEMPLATE_ARGUMENTS
    ValueType B_PLUS_TREE_INTERNAL_PAGE_TYPE::Lookup(const KeyType &key, const KeyComparator &comparator) const {
    int l=0,r=GetSize()-1;
    if (comparator(key, array[1].first) < 0) return array[0].second;
    else{
    while(l<r){
    int mid=(l+r)>>1;
    if(comparator(key,array[mid].first)<0)r=mid;
    else if(comparator(key, array[mid].first) > 0) l=mid+1;
    else return array[mid].second;
    }
    }
    return array[r].second;
    }
    • 找到Leaf page之后

    判断该元素是否已经在树中

    b. 进行分裂

    INDEX_TEMPLATE_ARGUMENTS
    bool BPLUSTREE_TYPE::InsertIntoLeaf(const KeyType &key, const ValueType &value, Transaction *transaction) {
    auto leaf = reinterpret_cast<BPlusTreeLeafPage<KeyType, ValueType,KeyComparator> *>(FindLeafPage(key, false,Operation::INSERT, transaction));
    if (leaf == nullptr) {
    return false;
    } // if already in the tree, return false
    ValueType v;
    if (leaf->Lookup(key, &v, comparator_)) {
    UnlockUnpinPages(Operation::INSERT, transaction);
    return false;
    }
    //case 1 keys in leaf page <m-1
    if (leaf->GetSize() < leaf->GetMaxSize()) {
    leaf->Insert(key, value, comparator_);
    }
  3. 分裂的步骤

  4. 调用split函数对叶子结点进行分割

    --- split的时候会产生一个含有m-m/2个关键字的新结点。注意把两个叶子结点连接起来。

    --- 然后调用InsertIntoParent

     // case 2 need to split
    else {
    leaf->Insert(key, value, comparator_);
    auto new_leaf = Split<BPlusTreeLeafPage<KeyType, ValueType, KeyComparator>>(leaf);
    new_leaf->SetNextPageId(leaf->GetNextPageId());
    leaf->SetNextPageId(new_leaf->GetPageId()); // insert the split key into parent
    InsertIntoParent(leaf, new_leaf->KeyAt(0), new_leaf, transaction);
    }
    UnlockUnpinPages(Operation::INSERT, transaction); return true;
    }
  5. InsertIntoParent

    case1-- 如果当前结点为根节点。则创建一个新的根节点。新根节点的子结点为分裂所得(经过split操作后)得到的两个结点

    INDEX_TEMPLATE_ARGUMENTS
    void BPLUSTREE_TYPE::InsertIntoParent(BPlusTreePage *old_node, const KeyType &key, BPlusTreePage *new_node,Transaction *transaction) {
    //case 1 create new root
    if (old_node->IsRootPage()) {
    auto page = buffer_pool_manager_->NewPage(&root_page_id_);
    if (page == nullptr) {
    throw "not page can used in InsertIntoParent";
    }
    assert(page->GetPinCount() == 1);
    auto root =reinterpret_cast<BPlusTreeInternalPage<KeyType, page_id_t,KeyComparator> *>(page->GetData());
    root->Init(root_page_id_,INVALID_PAGE_ID,internal_max_size_);
    root->PopulateNewRoot(old_node->GetPageId(), key, new_node->GetPageId()); old_node->SetParentPageId(root_page_id_);
    new_node->SetParentPageId(root_page_id_); //TODO update to new root_page_id
    UpdateRootPageId(false);
    //TODO unpin
    buffer_pool_manager_->UnpinPage(new_node->GetPageId(), true);
    buffer_pool_manager_->UnpinPage(root->GetPageId(), true); }

    case2 -- 否则要递归上述的过程

    a. 先找分裂产生结点的父亲结点。如果可以直接插入则直接插入

    b. 否则需要分裂

  //case2  insert into parent
else {
auto parent_page = buffer_pool_manager_->FetchPage(old_node->GetParentPageId());
if (parent_page == nullptr) {
throw "no old_node parent page can used";
}
auto internal =reinterpret_cast<BPlusTreeInternalPage<KeyType, page_id_t,KeyComparator> *>(parent_page->GetData());
// case 2.a insert directly
if (internal->GetSize() < internal->GetMaxSize()) {
internal->InsertNodeAfter(old_node->GetPageId(), key, new_node->GetPageId());
new_node->SetParentPageId(internal->GetPageId());
buffer_pool_manager_->UnpinPage(new_node->GetPageId(), true);
}
//case 2.b the parent node need to split
else {
page_id_t page_id;
auto new_page = buffer_pool_manager_->NewPage(&page_id);
if (new_page == nullptr) {
throw "no page can used while InsertIntoParent";
}
auto virtual_node =reinterpret_cast<BPlusTreeInternalPage<KeyType, page_id_t,KeyComparator> *>(new_page->GetData());
virtual_node->Init(page_id,old_node->GetParentPageId(),internal_max_size_);
virtual_node->SetSize(internal->GetSize());
for (int i = 1, j = 0; i <=internal->GetSize(); i++,j++) {
if (internal->ValueAt(i-1) == old_node->GetPageId()) {
virtual_node->SetKeyAt(j, key);
virtual_node->SetValueAt(j, new_node->GetPageId());
j++;
}
if (i < internal->GetSize()) {
virtual_node->SetKeyAt(j, internal->KeyAt(i));
virtual_node->SetValueAt(j, internal->ValueAt(i));
}
}
assert(virtual_node->GetSize() == virtual_node->GetMaxSize());
auto new_internal =Split<BPlusTreeInternalPage<KeyType, page_id_t, KeyComparator>>(virtual_node); internal->SetSize(virtual_node->GetSize() + 1);
for (int i = 0; i < virtual_node->GetSize(); ++i) {
internal->SetKeyAt(i + 1, virtual_node->KeyAt(i));
internal->SetValueAt(i + 1, virtual_node->ValueAt(i));
} // set new node parent page id
if (comparator_(key, new_internal->KeyAt(0)) < 0) {
new_node->SetParentPageId(internal->GetPageId());
} else if (comparator_(key, new_internal->KeyAt(0)) == 0) {
new_node->SetParentPageId(new_internal->GetPageId());
} else {
new_node->SetParentPageId(new_internal->GetPageId());
old_node->SetParentPageId(new_internal->GetPageId());
} // TODO unpin and delete virtual page
buffer_pool_manager_->UnpinPage(new_node->GetPageId(), true);
buffer_pool_manager_->UnpinPage(virtual_node->GetPageId(), false);
buffer_pool_manager_->DeletePage(virtual_node->GetPageId()); InsertIntoParent(internal, new_internal->KeyAt(0), new_internal);
} buffer_pool_manager_->UnpinPage(internal->GetPageId(), true);
}
}

好了实验2的第一部分就到这里了。整个实验都已经写完啦。剩下就是优化代码,写博客记录了,所以实验2的第二部分也会很快更新的。这里面的代码不是很详细。等到第二部分写完之后,会一整个完全上传到GitHub上的。

附上一个pass的截图完成第一部分

CMU数据库(15-445)实验2-b+树索引实现(上)的更多相关文章

  1. InnoDB的B+树索引使用

    何时使用索引 并不是在所有的查询条件下出现的列都需要添加索引.对于什么时候添加B+树索引,我的经验是访问表中很少一部分行时,使用B+树索引才有意义.对于性别字段.地区字段.类型字段,它们可取值的范围很 ...

  2. CMU数据库(15-445)Lab3- QUERY EXECUTION

    Lab3 - QUERY EXECUTION 实验三是添加对在数据库系统中执行查询的支持.您将实现负责获取查询计划节点并执行它们的executor.您将创建执行下列操作的executor Access ...

  3. CMU数据库(15-445)Lab0-环境搭建

    0.写在前面 从这篇文章开始.开一个新坑,记录以下自己做cmu数据库实验的过程,同时会分析一下除了要求我们实现的代码之外的实验自带的一些代码.争取能够对实现一个数据库比较了解.也希望能写进简历.让自己 ...

  4. Elasticsearch 中为什么选择倒排索引而不选择 B 树索引

    目录 前言 为什么全文索引不使用 B+ 树进行存储 全文检索 正排索引 倒排索引 倒排索引如何存储数据 FOR 压缩 RBM 压缩 倒排索引如何存储 字典树(Tria Tree) FST FSM 构建 ...

  5. MySQL:InnoDB存储引擎的B+树索引算法

    很早之前,就从学校的图书馆借了MySQL技术内幕,InnoDB存储引擎这本书,但一直草草阅读,做的笔记也有些凌乱,趁着现在大四了,课程稍微少了一点,整理一下笔记,按照专题写一些,加深一下印象,不枉读了 ...

  6. B树索引

    在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,SQL Server仍然可以实现应有的功能.但索引可以在大多数情况下大大提升查询性能高.在OLAP中尤其明显,要完全理解索引的 ...

  7. B+树索引和哈希索引的区别——我在想全文搜索引擎为啥不用hash索引而非得使用B+呢?

    哈希文件也称为散列文件,是利用哈希存储方式组织的文件,亦称为直接存取文件.它类似于哈希表,即根据文件中关键字的特点,设计一个哈希函数和处理冲突的方法,将记录哈希到存储设备上. 在哈希文件中,是使用一个 ...

  8. 浅谈B+树索引的分裂优化(转)

    http://www.tamabc.com/article/85038.html 从MySQL Bug#67718浅谈B+树索引的分裂优化   原文链接:http://hedengcheng.com/ ...

  9. B树索引和位图索引的区别!

    B树索引主键和唯一性约束字段的B树索引,效率几乎和海量数据没有关系. 键值重复率低的字段比较适合使用B树索引. 位图索引键值重复率高的字段比较适合使用位图索引.count.and.or.in这些特定的 ...

随机推荐

  1. Java获取到异常信息进行保存(非Copy)

    吐槽:不知道从什么时候开始,各大博客网站的文章开始各种复制粘贴,想好好找一个解决方法,搜索出来的博客基本上千篇一律,主要是能解决问题也还行,还解决不了问题这就恶心了.... 所以被迫自己写一篇文章,然 ...

  2. 持久层之 MyBatis: 第三篇 :缓存 And 高级查询

    MyBatis入门到精通3 缓存机制 Mybatis一级缓存测试 Mybatis二级缓存测试 高级查询 表关系说明 一对一查询 一对多查询 多对多查询 缓存机制 正如大多数持久层框架一样,MyBati ...

  3. robotframework中的参数展开

    robot调用关键字传参的方式是用分隔符分开不同参数,如 keyword arg1 arg2 arg3 arg4 当参数中传入了使用@符号的列表变量时,@符号会将列表展开: @{list1}= Cre ...

  4. python函数收集不确定数量的值

    python写函数的时候,有时候会不确定到底传入多少值. 首先是,*args,单星号参数收集参数: 1 #!usr/bin/python 2 #-*-coding:utf-8-*- 3 4 #定义一个 ...

  5. C#中的深度学习(四):使用Keras.NET识别硬币

    在本文中,我们将研究一个卷积神经网络来解决硬币识别问题,并且我们将在Keras.NET中实现一个卷积神经网络. 在这里,我们将介绍卷积神经网络(CNN),并提出一个CNN的架构,我们将训练它来识别硬币 ...

  6. Web项目访问在C盘的图片(不在当前项目路径下的图片)

    使用ASPX页面处理 前台显示 <img src="/UeImg.aspx?path=C:/YxFile/ueditor/upload/image/20200211/637170508 ...

  7. Git 使用中遇见的各种问题及解决办法

    一.修改提交代码的用户名以及提交邮箱,(推荐使用方法2,一劳永逸) 方法1(修改.git/config文件): step1:进入工程.git文件夹 step2:vim config step3:末行添 ...

  8. 迁移sqlserver数据到MongoDb

    前言 随着数据量的日积月累,数据库总有一天会不堪重负的,除了通过添加索引.分库分表,其实还可以考虑一下换个数据库.我强烈推荐使用MongoDb,我举例说一下我的经历:我的项目中有一张表的数据大概是30 ...

  9. SM4

    整体结构 T变换 SM4解密的合理性证明 秘钥扩展

  10. 第14章节 BJROBOT karto 算法构建地图【ROS全开源阿克曼转向智能网联无人驾驶车】

    建地图前说明:请确保你的小车已经校正好 IMU.角速度.线速度,虚拟机配置好 ROS 网络的前提进行,否则会造成构建地图无边界.虚拟机端无法正常收到小车主控端发布的话题数据等异常情况!! 1.把小车平 ...