P2 B+树索引

文章目录

    • Task1 B+树页
      • B+树页
      • B+树内部结点
      • B+树叶子结点
    • Task2 B+树操作
      • Task2 B+树插入和搜索的单一值
        • 插入单一值
        • 搜索单一值
      • Task2 B+树删除
    • Task3 叶子扫描的迭代器
    • Task4 并行索引

Task1 B+树页

B+树页

实际上是每个B+树页面的标题部分,包含叶子页面和内部页面共享的信息。

报头格式(大小以字节为单位,共12字节):
| PageType (4) | CurrentSize (4) | MaxSize (4) |// define page type enum
enum class IndexPageType { INVALID_INDEX_PAGE = 0, LEAF_PAGE, INTERNAL_PAGE };
// 成员变量,内部页和叶页共享的属性
IndexPageType page_type_ __attribute__((__unused__));
int size_ __attribute__((__unused__));      // 键值对的数量,内部页的第一个键为无效值
int max_size_ __attribute__((__unused__));  // 键值对的容量

B+树内部结点

在内部页面中存储n个索引键和n+1个子指针(page_id)。

指针PAGE_ID(i)指向一个子树,其中所有键K满足:K(i) <= K < K(i+1)。

注意:由于键的数量不等于子指针的数量,所以第一个键总是无效的。也就是说,任何搜索/查找都应该忽略第一个键。

内部页面格式(键按递增顺序存储):
| HEADER | KEY(1)+PAGE_ID(1) | KEY(2)+PAGE_ID(2) | ... | KEY(n)+PAGE_ID(n) |#define MappingType std::pair<KeyType, ValueType>
//key -> page_id 的映射
MappingType array_[0];

B+树叶子结点

只支持唯一键。

叶页格式(键按顺序存储):
| HEADER | KEY(1) + RID(1) | KEY(2) + RID(2) | ... | KEY(n) + RID(n)HEADER格式(大小为字节,共16字节):
| PageType (4) | CurrentSize (4) | MaxSize (4) |  NextPageId (4)page_id_t next_page_id_;  // 先定位到一个叶子,再顺序向后扫描
//key->record_id 的映射,record id = page id combined with slot id
MappingType array_[0];

Task2 B+树操作

大部分操作在b_plus_tree.cpp实现

Context类用于记录操作B+树过程中需要记忆的路径、读写保护等

class Context {public:// 当您插入/删除B+树时,将头页的写保护存储在这里。// 当您想要解锁所有内容时,请记住取消标题页保护并将其设置为空值,即header_page.reset()std::optional<WritePageGuard> header_page_{std::nullopt};//在这里保存根页面id,以便更容易地知道当前页面是否是根页面。page_id_t root_page_id_{INVALID_PAGE_ID};// 要修改的页面的写保护的队列。std::deque<WritePageGuard> write_set_;// 读保护队列std::deque<ReadPageGuard> read_set_;auto IsRootPage(page_id_t page_id) -> bool { return page_id == root_page_id_; }
};

Task2 B+树插入和搜索的单一值

插入单一值

BPLUSTREE_TYPE::Insert()实现,若BPLUSTREE_TYPE::header_page_id_属性标识的根节点的页(BPlusTreeHeaderPage)的根节点页id无效,说明没有根页面,调用缓冲区的NewPage()新建root页,然后强转为BPlusTreeLeafPage类型,此时根节点就是叶子节点,然后BPlusTreeLeafPage::InsertAtLast()根叶后插k-v,标识正在使用该页,清空header_page_(检查header_page_id_标识的根节点页时,页就存在这里)。若有根节点就获取根节点页id,调用InsertIntoLeaf()

BPLUSTREE_TYPE::InsertIntoLeaf()开头就调用了BPLUSTREE_TYPE::FindLeaf(),此函数的作用是为插入/删除结点操作寻找目标叶,结果存在B+树类的Context类成员的write_set_中。回到函数,取出查找到的页,强转为BPlusTreeLeafPage后直接调用BPlusTreeLeafPage::Insert()插入k-v,若插入失败说明是重复键值对,清空header_page_write_set_后退出,并且若叶页大小没超过叶子最大容量则此插入是安全插入,清空header_page_write_set_后退出,剩下的情况就是叶页满了,需要分裂,调用Split()进行分裂,返回的新页引用是满页分裂出的兄弟叶页,之后调用InsertIntoParent()修改父结点结构。

BPLUSTREE_TYPE::InsertIntoParent()是个直接递归,在函数开头有个判断write_set_内的锁有一个时,这些锁是在InsertIntoLeaf()中调用FindLeaf()时产生的,表示修改该叶子会导致write_set_中的这些结点页被修改,即最接近叶子的安全结点到叶子节点这一路径上的所有结点的锁。在write_set_中只剩一个页时,NewPage()新建一个页,当作新根节点,然后旧根节点和分裂出的新结点做新根的子节点,返回。通过write_set_获取父结点,然后调用BPlusTreeInternalPage::InsertAfterValue()将分裂后产生的新节点插入父节点,write_set_弹出旧结点(原来不安全的结点)。当父节点的大小超过了最大容量,则父节点不安全,调用Split()分裂并调用InsertIntoParent()递归,若是安全结点,则清理header_page_write_set_,结束。

BPLUSTREE_TYPE::Split()分裂结点,返回新的兄弟页。NewPage()创建新页,叶子节点分裂就将新页和待分裂的页强转为叶页,初始化新叶页后旧叶页调用BPlusTreeLeafPage::SplitLeafTo(),取出旧叶页的后一半数据给新叶页,并且设置旧叶页和新叶页的next_page_id。最后设置正在使用新叶页,然后返回。若分裂内部节点,将新页和待分裂的页强转为内部结点页,新结点页初始化后旧结点页调用BPlusTreeInternalPage::SplitInternalTo()取内部节点的最大容量加一的一半(后半段)的内容给新内部节点,限定结点大小。设置新结点页正在使用后返回。

这里我不理解的是在InsertIntoParent()中仅仅用if (ctx.write_set_.size() == 1)就确定是根节点分裂

搜索单一值

BPLUSTREE_TYPE::GetValue()实现,获取头部页的写锁header_page_,通过它获得根节点id,将根节点读锁加入read_set_然后强转为BPlusTreePage,清空header_page_,强转结点为BPlusTreeInternalPage,调用BPlusTreeInternalPage::FindInternelKey()按key二分查找中间节点的子节点引用,查到的页加读锁,加入read_set_然后将原先在read_set_中的父页弹出,这样就实现了读锁的"下沉",之后进入循环直到页是叶页。循环出来后将页强转为BPlusTreeLeafPage类,之后调用BPlusTreeLeafPage::FindKey()二分查找叶子节点的值,找到后组装传参vector。

Task2 B+树删除

BPLUSTREE_TYPE::Remove()实现,获取头部页写锁并得到根节点id,调用FindLeaf()查找key,获取查到的页强转成BPlusTreeLeafPage,调用BPlusTreeLeafPage::DeleteKey()先二分查找叶子结点key,若没有调用DeleteKeyAt()删除数组中对应的k-v,删除失败就清理header_page_write_set_并退出,若删除的k-v所在的叶页大小大于等于最小容量(叶子结点是max_size_/2,内部节点是(max_size_+1)/2)时,即为安全删除,清空write_set_并退出,若根节点时叶子,叶子根节点有一个键值对就能退出。最后都没有退出的就是需要非安全删除情况,可能会有修改父节点key、小于最小尺寸需从兄弟节点处拿甚至兄弟节点也不足直接合并,调用DealNode()合并或删除叶页。

BPLUSTREE_TYPE::DealNode()是个间接递归,它调用Merge()Merge()调用它。处理借取键值对或合并节点,write_set_的最后一个值是这个函数体需要处理的不安全的节点(已进行删除工作)。

  • 若是根节点

    • 根叶:清理write_set_header_page_
    • 不是叶:强转为内部节点,取第0个的引用做根节点id(header_page_强转为BPlusTreeHeaderPage可取root_page_id_

    返回

  • 不是根

    write_set_取出该页的父页,强转为内部节点

    • 是叶
      强转为叶页,父页调用FindInternelKey()二分查找叶子key获得index

      左边有值,有左兄弟(index > 0):取左兄弟(父页的index-1)的写锁存入write_set_,强转为叶页后保存变量

      右边有值,有右兄弟(index < 父页容量-1):取右兄弟(父页的index+1)的写锁存入write_set_,强转为叶页后保存变量

      • 左右兄弟都在(左叶页变量 && 右叶页变量):
        • 两个兄弟都不足(兄弟大小 <= 最小容量):调用Merge()与右节点合并,返回
        • 右边有剩余(右兄弟大小 > 最小容量):清空header_page_,向兄弟节点借键值对表示父页是安全节点,释放write_set_中父页的祖先节点及根的锁,叶页调用LendFromBrother()向右兄弟借,之后父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
        • 左边有剩余(else):清空header_page_,释放write_set_中父页的祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
      • 只有左兄弟(左叶页变量):
        • 左兄弟不足(左兄弟大小 <= 最小容量):调用Merge()与左兄弟合并,返回
        • 左边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的左兄弟结点在父页的索引,然后清理write_set_
      • 只有右兄弟(右叶页变量):
        • 右兄弟不足(右兄弟大小 <= 最小容量):调用Merge()与右兄弟合并,返回
        • 右边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向右兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
    • 是内部结点

      强转为内部结点页,父页调用FindInternelKey()二分查找内部结点获得index

      左边有值,有左兄弟(index > 0):取左兄弟(父页的index-1)的写锁存入write_set_,强转为内部结点页后保存变量

      右边有值,有右兄弟(index < 父页容量-1):取右兄弟(父页的index+1)的写锁存入write_set_,强转为内部结点后保存变量

      • 左右兄弟都在(左结点页变量 && 右结点页变量):
        • 两个兄弟都不足(兄弟大小 <= 最小容量):调用Merge()与右节点合并,返回
        • 右边有剩余(右兄弟大小 > 最小容量):清空header_page_,释放write_set_中父页的祖先节点及根的锁,内部结点页调用LendFromBrother()向右兄弟借,之后父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
        • 左边有剩余(else):清空header_page_,释放write_set_中父页的祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_
      • 只有左兄弟(左结点页变量):
        • 左兄弟不足(左兄弟大小 <= 最小容量):调用Merge()与左兄弟合并,返回
        • 左边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向左兄弟借,父页调用SetKeyAt()更新父页中的左兄弟结点在父页的索引,然后清理write_set_
      • 只有右兄弟(右结点页变量):
        • 右兄弟不足(右兄弟大小 <= 最小容量):调用Merge()与右兄弟合并,返回
        • 右边有剩余:清空header_set_,释放write_set_中父页祖先节点及根的锁,叶页调用LendFromBrother()向右兄弟借,父页调用SetKeyAt()更新父页中的右兄弟结点在父页的索引,然后清理write_set_

BPLUSTREE_TYPE::Merge()总是把右边节点的键值对转移给左边,再删除右边节点,这样方便更新NextPageId,若传入的经删除操作的页是叶子节点,将该页和兄弟页强转为叶页后,判断是右兄弟,兄弟结点就调用BPlusTreeLeafPage::MoveAllTo()将自己的k-v顺序插入该页的末尾,将该页的NextPageId设置为右兄弟的,设置删除结点索引为右兄弟索引(该页索引+1)。若判断是左兄弟,该页调用BPlusTreeLeafPage::MoveAllTo()将自己的k-v顺序插入左兄弟的尾部,左兄弟的NextPageId设置为该页的,设置删除节点索引为该页索引。若进行删除操作的页是内部结点,强转该页和兄弟页为内部节点页,判断是右兄弟就把兄弟k-v插入该页末尾,设置删除索引为兄弟,反之同理。之后将write_set_中该页父页的孩子出栈解锁,只剩父页和其祖先节点,把父页强转为内部节点页,调用DeleteKeyAt()将删除结点索引指定的结点页删除。若父页大小大于等于最小容量(安全删除),清理write_set_后就可返回,若是根结点,内部根节点右两个键值对(容量大于1)就行,返回,其他情况就是不安全删除,调用DealNode()进行间接递归。

BPlusTreeLeafPage::LendFromBrother()从brother移动一个键值对过来,兄弟调用KeyAt()ValueAt(),针对传入是否是左兄弟,将k-v插入结点的头或尾并设置兄弟的大小或删除k-v,最后返回新key。

Task3 叶子扫描的迭代器

必须添加一个c++迭代器,以有效地支持对叶页中的数据进行有序扫描。基本思想是存储兄弟指针,这样就可以有效地遍历叶页,然后实现一个迭代器,按顺序遍历每个叶页中的每个键值对。

Begin()End()函数在b_plus_true.cpp中,而迭代器类在index_iterator.cpp中实现

//索引迭代器类的私有属性
BPlusTreeLeafPage<KeyType, ValueType, KeyComparator> *leaf_page_{nullptr};  // 此迭代器指向的叶子
int index_{-1};  // 表示此迭代器现在指向leaf_page_中某叶页的第几个键值对
page_id_t my_page_id_;//表示迭代器指向的leaf_page_中某叶页的页id
BufferPoolManager *bpm_{nullptr};

重载了* ++ == !=四个符号来对索引迭代器进行操作

BPLUSTREE_TYPE::Begin()有重载,分别对应处理没给key和给了key的情况。没给就直接调用FindLeafPage()传入空key并指定找最左边的k-v,这样就返回了叶页链表首页,然后强转为叶页,构造索引迭代器后返回。传入key的Begin()多加了一层检测传回的页是否真的有key,没有说明FindLeafPage()没有找到,返回空索引迭代器。

BPLUSTREE_TYPE::End()同无参Begin()只不过指定找的是最右边的k-v(即index=internal_page->GetSize()-1

BPLUSTREE_TYPE::FindLeafPage()无锁获取根节点,根据传入的indextype判断需要的是最左/右的页还是进行k-v查找。

Task4 并行索引

见Task 2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/116326.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Docker】Dockerfile常用指令

参考官方文档&#xff1a;https://docs.docker.com/engine/reference/builder/ Dockerfile常用指令 指令说明from基础镜像&#xff0c;当前镜像基于&#xff08;依赖&#xff09;哪个镜像maintainer镜像的维护者和邮箱run镜像构建时需要执行的命令workdir镜像的工作目录expos…

基于springboot实现基于Java的超市进销存系统项目【项目源码+论文说明】

基于springboot实现基于Java的超市进销存系统演示 摘要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;超市进销存系统也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#x…

最详细STM32,cubeMX外部中断

这篇文章将详细介绍 cubeMX外部中断的配置&#xff0c;实现过程。 文章目录 前言一、外部中断的基础知识。二、cubeMX 配置外部中断三、自动生成的代码解析四、代码实现。总结 前言 实验开发板&#xff1a;STM32F103C8T6。所需软件&#xff1a;keil5 &#xff0c; cubeMX 。实…

09 创建型模式-建造者模式

1.建造者模式介绍&#xff1a; 建造者模式 (builder pattern), 也被称为生成器模式 , 是一种创建型设计模式 定义: 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不 同的表示。 2.建造者模式要解决的问题 建造者模式可以将部件和其组装过程分开&am…

【Unity程序技巧】公共Update管理器

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

Spring Boot中捕获异常错误信息并将其保存到数据库中

Spring Boot中捕获异常错误信息并将其保存到数据库中: 1.创建数据库表&#xff1a; 首先&#xff0c;您需要创建一个用于存储异常信息的数据库表。可以使用SQL脚本或者使用Hibernate实体类来创建表。以下是一个用于存储异常信息的表的示例SQL&#xff1a; CREATE TABLE erro…

【29】c++设计模式——>策略模式

策略模式 C中的策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许在运行时选择算法的行为。策略模式通过将算法封装成独立的类&#xff0c;并且使它们可以互相替换&#xff0c;从而使得算法的变化独立于使用算法的客户端。 策略模式通…

图像语义分割 pytorch复现DeepLab v1图像分割网络详解以及pytorch复现(骨干网络基于VGG16、ResNet50、ResNet101)

图像语义分割 pytorch复现DeepLab v1图像分割网络详解以及pytorch复现&#xff08;骨干网络基于VGG16、ResNet50、ResNet101&#xff09; 背景介绍2、 网络结构详解2.1 LarFOV效果分析 2.2 DeepLab v1-LargeFOV 模型架构2.3 MSc&#xff08;Multi-Scale&#xff0c;多尺度(预测…

vim 使用文档笔记

1. i&#xff1a;进入编辑模式 2. ESC&#xff1a;进入一般命令模式 3. h 或 ←&#xff1a;光标向左移动一个字符 4. j 或 ↓&#xff1a;光标向下移动一个字符 5. k 或 ↑&#xff1a;光标向上移动一个字符 6. l 或 →&#xff1a;光标向右移动一个字符 7. num&#xf…

Matlab论文插图绘制模板第122期—函数折线图(fplot)

本期分享的是函数折线图的绘制模板。​ 所谓函数折线图&#xff0c;就是将自定义线函数进行可视化表达​。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行下载。有需要的朋友可以关注同名公号【阿昆的…

【JavaEE】网络编程---TCP数据报套接字编程

一、TCP数据报套接字编程 1.1 ServerSocket API ServerSocket 是创建TCP服务端Socket的API ServerSocket 构造方法&#xff1a; ServerSocket 方法&#xff1a; 1.2 Socket API Socket 是客户端Socket&#xff0c;或服务端中接收到客户端建立连接&#xff08;accept方法&…

el-table表格的一些操作-表格实现单选、多选

表格实现多选 <el-table:data"dataList"borderselection-change"handleSelectionChange">//多选框<el-table-column type"selection" width"55" align"center" /></el-table> handleSelectionChange(val…

浅谈兼容性测试的关键步骤

兼容性测试是确保应用程序在多样化的技术环境中正常运行的关键步骤。它有助于提高用户满意度&#xff0c;扩大市场覆盖范围&#xff0c;同时确保法规合规性。通过正确执行兼容性测试&#xff0c;企业可以确保其应用程序在各种平台上提供一致的卓越用户体验&#xff0c;从而增强…

#电子电器架构 —— 车载网关初入门

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 PS:小细节,本文字数7000+,详细描述了网关在车载框架中的具体性能设置。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他…

现在游戏出海有多少优势?

国内游戏市场趋于饱和&#xff0c;但是国外市场潜力仍然可观&#xff0c;因此很多人选择游戏出海&#xff0c;那么现在游戏出海有多少优势呢&#xff1f; 1、市场潜力 全球游戏市场潜力巨大&#xff0c;增长迅速。中国游戏公司具有强大的研发能力和创新能力&#xff0c;能够开…

在edge浏览器中安装好了burp的ca证书,浏览器依旧不能访问https的原因

在edge浏览器中安装好了burp的ca证书&#xff0c;浏览器依旧不能访问https的原因 1.SwitchyOmega代理插件设置2.CA证书方法1方法2 1.SwitchyOmega代理插件设置 严格安装以下图片执行&#xff0c;不可少写或多写 2.CA证书 方法1 下载好证书&#xff0c;先导入到edge浏览器的中…

人工智能和机器学习:走向智能未来的关键

人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;是当今IT领域中最令人振奋的发展方向之一。从自动驾驶汽车到智能助手&#xff0c;AI技术的应用正在不断扩展&#xff0c;重新定义着我们的生活方式和商业模式。在这个文章中&#xff0c;我们将深入探讨…

Qt 案例 使用QNetworkReply或者URLDownloadToFile 下载http、https资源到本地路径

Qt 使用QNetworkReply或者URLDownloadToFile两种不同方式下载http、https链接资源文件&#xff0c;并且获取下载进度。 目录 一、 使用 URLDownloadToFile 下载二、 使用 QNetworkReply 下载三、 打包好的可执行程序示例下载四、 会员或订阅专栏下载源码 一、 使用 URLDownload…

感谢我的岗位

我很高兴能够分享我干了一年嵌入式软件工程师岗位的经验。作为一名嵌入式软件工程师&#xff0c;我在这一年中积累了很多经验和技能&#xff0c;也遇到了许多挑战。在这篇文章中&#xff0c;我将分享我的经验&#xff0c;并探讨我从这个过程中学到的东西。 首先&#xff0c;我…