[已完结]CMU数据库(15-445)实验2-B+树索引实现(下)
4. Index_Iterator实现
这里就是需要实现迭代器的一些操作,比如begin、end、isend等等
下面是对于IndexIterator的构造函数
template <typename KeyType, typename ValueType, typename KeyComparator>
IndexIterator<KeyType, ValueType, KeyComparator>::
IndexIterator(BPlusTreeLeafPage<KeyType, ValueType, KeyComparator> *leaf,
int index_, BufferPoolManager *buff_pool_manager):
leaf_(leaf), index_(index_), buff_pool_manager_(buff_pool_manager) {}
1. 首先我们来看begin函数的实现
- 利用key值找到叶子结点
- 然后获取当前key值的index就是begin的位置
INDEX_TEMPLATE_ARGUMENTS
INDEXITERATOR_TYPE BPLUSTREE_TYPE::Begin(const KeyType &key) {
auto leaf = reinterpret_cast<BPlusTreeLeafPage<KeyType, ValueType,KeyComparator> *>(FindLeafPage(key, false));
int index = 0;
if (leaf != nullptr) {
index = leaf->KeyIndex(key, comparator_);
}
return IndexIterator<KeyType, ValueType, KeyComparator>(leaf, index, buffer_pool_manager_);
}
2. end函数的实现
- 找到最开始的结点
- 然后一直向后遍历直到
nextPageId=-1结束 - 这里注意需要重载
!=和==
end函数
INDEX_TEMPLATE_ARGUMENTS
INDEXITERATOR_TYPE BPLUSTREE_TYPE::end() {
KeyType key{};
auto leaf= reinterpret_cast<BPlusTreeLeafPage<KeyType, ValueType,KeyComparator> *>( FindLeafPage(key, true));
page_id_t new_page;
while(leaf->GetNextPageId()!=INVALID_PAGE_ID){
new_page=leaf->GetNextPageId();
leaf=reinterpret_cast<BPlusTreeLeafPage<KeyType, ValueType,KeyComparator> *>(buffer_pool_manager_->FetchPage(new_page));
}
buffer_pool_manager_->UnpinPage(new_page,false);
return IndexIterator<KeyType, ValueType, KeyComparator>(leaf, leaf->GetSize(), buffer_pool_manager_);
}
==和 !=函数
bool operator==(const IndexIterator &itr) const {
return this->index_==itr.index_&&this->leaf_==itr.leaf_;
}
bool operator!=(const IndexIterator &itr) const {
return !this->operator==(itr);
}
3. 重载++和*(解引用符号)
- 重载++
简单的index++然后设置nextPageId即可
template <typename KeyType, typename ValueType, typename KeyComparator>
IndexIterator<KeyType, ValueType, KeyComparator> &IndexIterator<KeyType, ValueType, KeyComparator>::
operator++() {
//
// std::cout<<"++"<<std::endl;
++index_;
if (index_ == leaf_->GetSize() && leaf_->GetNextPageId() != INVALID_PAGE_ID) {
// first unpin leaf_, then get the next leaf
page_id_t next_page_id = leaf_->GetNextPageId();
auto *page = buff_pool_manager_->FetchPage(next_page_id);
if (page == nullptr) {
throw Exception("all page are pinned while IndexIterator(operator++)");
}
// first acquire next page, then release previous page
page->RLatch();
buff_pool_manager_->FetchPage(leaf_->GetPageId())->RUnlatch();
buff_pool_manager_->UnpinPage(leaf_->GetPageId(), false);
buff_pool_manager_->UnpinPage(leaf_->GetPageId(), false);
auto next_leaf =reinterpret_cast<BPlusTreeLeafPage<KeyType, ValueType,KeyComparator> *>(page->GetData());
assert(next_leaf->IsLeafPage());
index_ = 0;
leaf_ = next_leaf;
}
return *this;
};
- 重载*
return array[index]即可
template <typename KeyType, typename ValueType, typename KeyComparator>
const MappingType &IndexIterator<KeyType, ValueType, KeyComparator>::
operator*() {
if (isEnd()) {
throw "IndexIterator: out of range";
}
return leaf_->GetItem(index_);
}
5. 并发机制的实现
0. 首先复习一下读写机制
- 读操作是可以多个进程之间共享latch的而写操作则必须互斥
- 加入
MaxReader数就是为了防止等待的️写进程饥饿
首先来看如果没有机制多线程会发生什么问题
- 线程T1想要删除44。
- 线程T2 想要查找41

- 假设T2在执行到D位置的时候又切换到线程T1
- 这个时候T1进行重新分配,会把41借到I结点上
- T1执行完成切换回T2这时候T2再去原来的执行寻找41就会找不到

就会出现下面的情况。

由此我们需要读写的存在
- 对于find操作
由于我们是只读操作,所以我们到下一个结点的时候就可以释放上一个结点的Latch

剩下的操作都是一样的
- 对于
delete则不一样
因为我们需要写操作
这里我们不能释放结点A的Latch。因为我们的删除操作可能会合并根节点。

到D的时候。我们会发现D中的38删除之后不需要进行合并,所以对于A和B的写Write是可以安全释放了

- 对于
Insert操作
这里我们就可以安全的释放掉A的锁。因为B中还有空位,我们插入是不会对A造成影响的

当我们执行到D这里发现D中已经满了。所以此时我们不会释放B的锁,因为我们会对B进行写操作

上面的算法虽然是正确的但是有瓶颈问题。由于只有一个线程可以获得写Latch。而插入和删除的时候都需要对头结点加写Latch。所以多线程在有许多个插入或者删除操作的时候,性能就会大打折扣

这里要引入乐观
乐观的假设大部分操作是不需要进行合并和分裂的。因此在我们向下的时候都是读Latch而不是写Latch。只有在叶子结点才是write Latch
- 从上到下都是读Latch。而且逐步释放
- 到叶子结点需要修改的时候才为写Latch。这个删除是安全的所以直接结束

- 当我们到最后一步发现不安全的时候。则需要像上面我们没有引入乐观的时候一样。重新执行一遍

B-Link Tree简介
延迟更新父结点
这里用一个来标记这里需要被更新但是还没有执行

这个时候我们执行其他操作也是正确的比如查找31

这里我们执行insert 33
当执行到结点C的时候。因为这个时候有另一个线程持有了write Latch。所以这个时候操作要执行。随后在插入33

最后一点补充关于扫描操作的
- 线程1在C结点上持有write Latch
- 线程2已经扫描完了结点B想要获得结点C的read Latch
这时候会发生问题,因为线程2无法拿到read Latch
这里有几种解决方法
- 可以等到T1的写操作完成
- 可以重新执行T2
- 可以直接让线程T2停止抢得这个Latch。

注意这里的Latch和Lock并不一样

1. 辅助函数UnlockUnpinPages的实现
- 如果是读操作则释放read锁
- 否则释放write锁
INDEX_TEMPLATE_ARGUMENTS
void BPLUSTREE_TYPE::
UnlockUnpinPages(Operation op, Transaction *transaction) {
if (transaction == nullptr) {
return;
}
for (auto page:*transaction->GetPageSet()) {
if (op == Operation::READ) {
page->RUnlatch();
buffer_pool_manager_->UnpinPage(page->GetPageId(), false);
} else {
page->WUnlatch();
buffer_pool_manager_->UnpinPage(page->GetPageId(), true);
}
}
transaction->GetPageSet()->clear();
for (const auto &page_id: *transaction->GetDeletedPageSet()) {
buffer_pool_manager_->DeletePage(page_id);
}
transaction->GetDeletedPageSet()->clear();
// if root is locked, unlock it
node_mutex_.unlock();
}
四个自带的解锁和上锁操作
/** Acquire the page write latch. */
inline void WLatch() { rwlatch_.WLock(); }
/** Release the page write latch. */
inline void WUnlatch() { rwlatch_.WUnlock(); }
/** Acquire the page read latch. */
inline void RLatch() { rwlatch_.RLock(); }
/** Release the page read latch. */
inline void RUnlatch() { rwlatch_.RUnlock(); }
这里的rwlatch是自己实现的读写锁类下面来探究一下这个类
由于c++ 并发编程我现在还不太会。。。所以就简单看一下啦后面学完并发编程再补充
WLock函数- 首先获取一个锁
- 用一个记号
writer_entered表示是否有写操作 - 如果之前已经有了现在的操作就需要等(这个线程处于阻塞状态)
- 当前如果有其他线程执行读操作。则仍需要阻塞(别人读的时候你不能写)
void WLock() {
std::unique_lock<mutex_t> latch(mutex_);
while (writer_entered_) {
reader_.wait(latch);
}
writer_entered_ = true;
while (reader_count_ > 0) {
writer_.wait(latch);
}
}
WunLock函数- 写标记置为false
- 然后通知所有的线程
void WUnlock() {
std::lock_guard<mutex_t> guard(mutex_);
writer_entered_ = false;
reader_.notify_all();
}
RLock函数- 如果当前有人在写或者已经有最多的人读了则阻塞
- 否则只需要让读的计数++
因为是允许多个线程一起读这样并不会出错
void RLock() {
std::unique_lock<mutex_t> latch(mutex_);
while (writer_entered_ || reader_count_ == MAX_READERS) {
reader_.wait(latch);
}
reader_count_++;
}
RUnLatch函数- 计数--
- 如果当前有人在写并且无人读的话需要通知所有其他线程
- 如果在计数--之前达到了最大读数,释放这个锁之后需要通知其他线程,现在又可以读了。
void RUnlock() {
std::lock_guard<mutex_t> guard(mutex_);
reader_count_--;
if (writer_entered_) {
if (reader_count_ == 0) {
writer_.notify_one();
}
} else {
if (reader_count_ == MAX_READERS - 1) {
reader_.notify_one();
}
}
}
6. Summary
好了终于磕磕绊绊的写完了Lab2.关于数据库的Lab2应该会停一段时间。这段时间要补一补深度学习(毕竟要毕业)然后赶工一下老师给的活。同时学一下c++并发编程和看一下侯捷老师的课程。
最后附上GitHub的
https://github.com/JayL-zxl/CMU15-445Lab
[已完结]CMU数据库(15-445)实验2-B+树索引实现(下)的更多相关文章
- CMU数据库(15-445)实验2-B+树索引实现(下+课上笔记)
4. Index_Iterator实现 这里就是需要实现迭代器的一些操作,比如begin.end.isend等等 下面是对于IndexIterator的构造函数 template <typena ...
- CMU数据库(15-445)实验2-b+树索引实现(上)
Lab2 在做实验2之前请确保实验1结果的正确性.不然你的实验2将无法正常进行 环境搭建地址如下 https://www.cnblogs.com/JayL-zxl/p/14307260.html 实验 ...
- Lenovo k860i 移植Android 4.4 cm11进度记录【上篇已完结】
2014.5.16 为了验证一下下载的CM11的源码有没有问题,决定编译一下cm官方支持的机器,手上正好有台nexus7 2012,就拿它为例测试一下在mac os x平台的整个编译过程. 1. 最开 ...
- [Android]如何导入已有的外部数据库
转自:http://www.cnblogs.com/xiaowenji/archive/2011/01/03/1925014.html 我们平时见到的android数据库操作一般都是在程序开始时创建一 ...
- CMU数据库(15-445)Lab0-环境搭建
0.写在前面 从这篇文章开始.开一个新坑,记录以下自己做cmu数据库实验的过程,同时会分析一下除了要求我们实现的代码之外的实验自带的一些代码.争取能够对实现一个数据库比较了解.也希望能写进简历.让自己 ...
- CMU数据库(15-445)Lab3- QUERY EXECUTION
Lab3 - QUERY EXECUTION 实验三是添加对在数据库系统中执行查询的支持.您将实现负责获取查询计划节点并执行它们的executor.您将创建执行下列操作的executor Access ...
- 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型
不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...
- 使用EF对已存在的数据库进行模块化数据迁移
注:本文面向的是已经对EF的迁移功能有所了解,知道如何在控制台下进行相关命令输入的读者 问题 最近公司项目架构使用ABP进行整改,顺带想用EF的自动迁移代替了以前的手工脚本. 为什么要替代? 请看下图 ...
- 如何把已有SQLSERVER数据库更名而且附加到数据库中?
如何把已有SQLSERVER数据库更名而且附加到数据库中? 例如:已有数据库:zrmaa,希望更名为jjsh 特别提醒:数据库名中不能加入下划线,因为数据库日志文件有下划线. 把数据库文件mdf和数据 ...
随机推荐
- 如何查看打印机的IP地址和MAC地址
1. 打开控制面板,选择设备和打印机: 2. 选中打印机,右键单机,选择打印机 "属性": 3. 选择web服务,可以直接查看打印机的IP地址或MAC地址,如下图所示: 4. ...
- 深度实战玩转算法, Java语言7个经典应用诠释算法精髓
深度实战玩转算法,以Java语言主讲,通过7款经典好玩游戏,真正将算法用于实际开发,由算法大牛ACM亚洲区奖牌获得者liuyubobobo主讲,看得见的算法,带领你进入一个不一样的算法世界,本套课程共 ...
- ML.net重新训练模型需要注意的事项。
ml.net是微软机器学习的东西,如果你的需求是需要一个固定的模型来进行操作的话那就按着官网的教程来就可以,但是大部分的模型可能不满足现有的需求,那么我们需要对模型进行重新训练. 重新训练模型有限制条 ...
- windows下plsql连接linux下的oracle数据库
windows下plsql连接linux下的oracle数据库 经过多方查找,终于找到解决办法,特此记录下来,共享之. PL/SQL Develorper:目前未发现可以在Linux系统中安装的版本. ...
- 96. Unique Binary Search Trees1和2
/* 这道题的关键是:动态表尽量的选取,知道二叉搜索树中左子树的点都比根节点小,右子树的点都比根节点大 所以当i为根节点,左子树有i-1个点,右子树有n-i个点,左右子树就可以开始递归构建,过程和一开 ...
- 【探索之路】机器人篇(5)-Gazebo物理仿真环境搭建_让机器人运动起来
如果完成了前两步,那么其实我们已经可以去连接我们的现实中的机器人了. 但是,做机器人所需要的材料还没有到,所以我们这里先在电脑平台上仿真一下.这里我们用到的就算gazebo物理仿真环境,他能很好的和R ...
- 所有CSS字体属性
font(在一个声明中设置所有的字体属性) font-family(指定文本的字体系列) font-size(指定文本的字体大小) font-style(指定文本的字体样式) font-variant ...
- chrome实现网页高清截屏(F12、shift+ctrl+p、capture)
打开需要载屏的网页,在键盘上按下F12,出现以下界面 上图圈出的部分有可能会出现在浏览器下方,这并没有关系.此时按下 Ctrl + Shift + P(Mac 为 ⌘Command +⇧Shift + ...
- Docker技术
- Redis的内存淘汰
Redis占用内存大小 我们知道Redis是基于内存的key-value数据库,因为系统的内存大小有限,所以我们在使用Redis的时候可以配置Redis能使用的最大的内存大小. 1.通过配置文件配置 ...