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. SpringBoot执行原理

    目录 [Toc] 一.执行原理: 每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中通过执行SpringApplication.run( ...

  2. Mysql联合索引的最左前缀原则以及b+tree

    软件版本mysql5.7 根据官网的文档 https://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html 查询条件要符合最左原 ...

  3. python 画图二(三维图,多轴图)

    import sys reload(sys) sys.setdefaultencoding('utf-8') import matplotlib.pyplot as plt import numpy ...

  4. Java JVM——5.Java虚拟机栈

    虚拟机栈概述 由于跨平台性的设计,Java 的指令都是根据栈来设计的.不同平台CPU架构不同,所以不能设计为基于寄存器的. 栈实现的优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功 ...

  5. Centos7路由设置

    再添加路由时,很多时候都是采用命令行用route添加的.但是在机器重启后.就失效了.这里也是参考了几位博主的经验 作出以下记载 一:路由表常用设置 1.route命令路由表常用设置: //添加到主机的 ...

  6. python常见的内置模块

    python的内置模块: os os.path sys time datetime hashlib base64 hmac random math 一.os模块: os模块是与操作系统交互的一个接口 ...

  7. MySQL-5.7.29-winx64解压缩版安装

    1.下载压缩包 https://dev.mysql.com/downloads/file/?id=491809 2.解压下载的文件(路径放在哪都可以) 3.配置环境变量 添加变量到path中 4.准备 ...

  8. Spring Cloud Config应用篇(九)

    一.SpringCloud Config 配置中心 1.1.配置中心说明 SpringCloud Config 服务器以下简称"配置中心". Spring Cloud Config ...

  9. 24位PCM采样数据转成16位算法,已实现PCM转WAV在线工具源码支持24bits、16bits、8bits

    目录 算法来源 js版24位PCM转8位.16位代码 js版8位.16位PCM转成24位 附:浏览器控制台下载数据文件代码 相关实现 最近收到几个24位的PCM录音源文件,Recoder库原有的PCM ...

  10. Redis缓存篇(一)Redis是如何工作的

    Redis提供了高性能的数据存取功能,所以广泛应用在缓存场景中,既能有效地提升业务应用的响应速度,还可以避免把高并发压力发送到数据库层. 因为Redis用作缓存的普遍性以及它在业务应用中的重要作用,所 ...