缓冲池管理器

开发环境搭建

克隆

git clone https://github.com/cmu-db/bustub.git

cd bustub/

切换分支

git checkout -b branchname v20221128-2022fall

创建docker镜像

docker build . -t bustub_img

创建容器

docker create -it --name bustub_container -v "E:/cmu/bustub":"/bustub" bustub_img bash

启动容器

docker start -ia bustub_container

查看有哪些已经启动的容器

docker ps

进入指定容器

docker exec -it 30eb49a811aa /bin/bash

Project0

编译

在/bustub/build生成makefile:cmake ...

报错:CMake Warning at CMakeLists.txt:47 (message):

!! We recommend that you use clang-12 for developing BusTub. You're using

GNU 11.4.0, which is not clang.
解决:在主目录的CMakeLists下增加2行 set(CMAKE_C_COMPILER "/usr/bin/clang-12")

set(CMAKE_CXX_COMPILER "/usr/bin/clang+±12")
或者:加编译选项:-DCMAKE_C_COMPILER=usr/bin/clang-12和-DCMAKE_CXX_COMPILER=/usr/bin/clang+±12
设置失效:ctrl+ship+p cmake配置 选择clang-12,然后重新打开一个终端

编译:make starter_trie_test

调试

launch.json

cpp 复制代码
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug",
            "program": "${workspaceFolder}/build/test/starter_trie_test",
            "args": [],
            "cwd": "${workspaceFolder}",
            "preLaunchTask": "make starter_trie_test"
        },
    ]
}

make starter_trie_test任务:这里使用-C指定makefile文件的路径在当前目录的build目录下

cpp 复制代码
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "shell",
			"label": "make starter_trie_test",
			"command": "make",
			"args": [
				"-C",
				"build",
				"starter_trie_test"
			]
		}
	]
}

Project1:缓冲池管理器

task1:可扩展哈希

目录大小:2^全局深度

全局深度: 取元素的低多少位 进行分类,放入不同的桶中

桶的局部深度:当有其它桶进行分裂使得目录扩张(全局深度增加),会导致未分裂的桶局部深度低于全局深度。这种情况下,如果该桶发生溢出,那么只进行桶分裂,全局深度不增加。
全局深度>局部深度 发生桶溢出时:只分裂不扩张 全局深度=局部深度 发生桶溢出时:既分裂又扩张

关键:扩张是把所有桶全部尾插到目录中,桶分裂时使用局部深度形成的掩码来找到指向当前桶的所有idx,将它们重新指向新的桶

,最后重新分配当前桶中的所有元素。

task2: LRU-K替换策略

  • 力扣146.LRU缓存

    std::list<std::pair<int,int>> ls;//k,v

    std::unordered_map<int, decltype(ls.begin())> lru_map;//k,list

    get:要将查看的这个key再次put

    put:先判断put的key是否已经存在,如果已经有了,就将当前旧的key删除,重新插入到头部。

  • LRU-K

    cpp 复制代码
    std::list<std::pair<frame_id_t, Frameinfo>> ls1;//frame_id 访问次数
    std::list<std::pair<frame_id_t, Frameinfo>> ls2;//frame_id 访问次数
    //使用map,可以O(1)时间复杂度 根据frame_id直接访问到list上各页面的访问次数
    //同时,可以直接操作list节点进行移动
    std::unordered_map<frame_id_t, decltype(ls1.begin())> m;//frame_id list1/list2的迭代器

    使用map保存{frame_id:list迭代器},就可以通过frame_id直接找到链表上的该页面对应信息,使用如下Frameinfo结构体保存每个页的信息。
    struct Frameinfo { size_t visits = 0;//访问次数 bool evictable_{false};//是否可驱逐 };

task3:缓冲池管理器

功能:系统向缓冲池管理器请求某指定page_id的页面,缓冲池管理器经过一系列操作,返回页面在内存中的地址。

具体操作:先在可拓展哈希中找该页面,可拓展哈希的list中存储的是(page_id,frame_id),根据得到的frame id即可在pages_数组中找到该页。如果可拓展哈希中没有该页面,就先看free_list还有没有空位,如果有,直接调用WritePage把Disk上的页面读取到内存,否则先调用LRU-K驱逐一个内存块。

原子变量的使用

  • 由于不允许拷贝,只能使用值初始化
    头文件#include
    atomic_int m(0);

size_t转int:
static_cast<int>(i)

变量 意义
page_table_ 可拓展哈希,保存(page_id,frame_id)
replacer_ LRU-K,主要是在需要驱逐页面时使用,传出frame_id
pages_ 以frame_id为下标,保存所有页面的数组
free_list_ 保存pages_中空闲页面的frame_id,一开始就被初始化为从0开始的int
page 页面,保存有该页面的page_id,实际数据是一个char数组

NewPgImp

在缓冲池中新增一个空白页面,并指定好页面的id。如果free_list已满,就尝试驱逐页面(如果页面是脏,需要写回磁盘),如果都无法驱逐,就返回nullptr

frame_id的获取 :首先检查空闲链表free_list_,如果有空闲frame_id就直接取出一个使用,然后添加新页面信息。如果没有空闲,就驱逐页面得到一个frame_id,然后添加新页面信息。
怎么驱逐页面 :调用Evict得到驱逐页面的frame_id后,需要检查该页是否为脏页,如果是,则需要写回磁盘。此外,还需要把该页信息从可拓展哈希中删除。
怎么添加新页面信息:AllocatePage函数会在定义的next_page_id上依次+1.每次只需要调用它获取新的page_id。由于所有页面都保存在pages_数组中,因此设置pages[frame_id]的信息即可。此外,还需要把页面信息写入可拓展哈希和LRU-K

FetchPgImp

读一个页面,先看是不是在缓冲池中,如果不在,需要从磁盘上读入

Project2:B+树

数据库支持多种索引方式:

哈希索引 全文索引 B+树索引

mysql数据库为什么使用B+树作为索引的数据结构

为了加快记录的查找,必须使用索引来避免对全表进行顺序查找。

二叉树:如果数据关键字恰好有顺序,会导致形成特别深的二叉树

平衡二叉树:避免了二叉树中形成线性链表的情况,但一个节点所保存的信息太少,无法有效利用到磁盘预读机制

多路平衡查找树(B树):很好利用了磁盘预读机制,但每个非叶子节点记录自己存储的数据,不利于区间查找,且导致可存储的节点数目大大减少。

B+树:所有数据存储在叶子节点中,且叶子节点被串成有序的双向链表,便于区间查找。关键设计在于,让一个节点的大小恰好等于一个块的大小(4K),这样减少了磁盘IO。内部节点只存储key和维持树形结构的指针,叶子节点只存储key和对应数据。

A是磁道,C是扇区,D是磁盘块(簇)由多个扇区组成

区分磁盘预读与cpu预取机制

磁盘预读:由于内存比磁盘的读写速度快,需要尽量减少磁盘IO次数。现在常见磁盘扇区大小为4K个字节,因此每次磁盘读取都至少将1页数据加载进内存。

cpu预取:内存的读写速度太慢,为了减小其与cpu的差距,在cpu内部引入了3级缓存,从内存读取数据时,cpu会试图预取64字节数据到缓存。

对于频繁写而不是频繁读的场景,使用LSM树比B+树更适合:例如日志系统

因为B+树的数据都存储在叶子节点中,叶子节点一般存储在磁盘中。每次写入数据都是要随机写入磁盘。

LSM树简介:至少由2棵树组成,1棵较小,存储在内存中,1棵较大,存储在磁盘中。内存中的树变大到某阈值,就批量写入到磁盘的树中(相当于有序链表的归并)

数据库存储引擎

mysql有2种常用的存储引擎:MyISAM、InnoDB

存储引擎的作用:数据先传输到存储引擎,再按照其规定存储格式保存到数据文件中。

MyISAM InnoDB
不支持事务 支持事务
存储速度快 存储速度慢些
不支持外键 支持外键

InnoDB默认定义的块大小是16KB,即每次磁盘IO,读取数据都是16KB的倍数

柔性数组成员

结构体的最后一个元素可以是大小未定的数组,被称作柔性数组

cpp 复制代码
#include <iostream>
using namespace std;
struct A
{
    int x;//4
    int str[];//大小未定
};
int main()
{
	cout<<sizeof(A)<<endl;//4
    A* p = new A;
    for (size_t i = 0; i < 10; i++)
    {
        p->str[i] = 1;
    }
	return 0;
}

std::lower_bound()和std::upper_bound()

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main(){
  	vector<int> num = {1,2,4,7,15,34}; 
	sort(num.begin(), num.end());//从小到大排序 
	int pos1=lower_bound(num.begin(), num.end(),7)-num.begin();    //返回数组中第一个大于或等于被查数的值 
	int pos2=upper_bound(num.begin(), num.end(),7)-num.begin();    //返回数组中第一个大于被查数的值
	cout<<pos1<<" "<<num[pos1]<<endl;//3 7
	cout<<pos2<<" "<<num[pos2]<<endl;//4 15
	return 0;	
}

叶子的查找与非叶子的查找

叶子:0开始找,只有等于和不等于两种情况

非叶子:1开始找(0处没保存值),使用lower_bound查找的最终位置t,如果结果大于当前数,则返回array_[t-1],如果等于当前数,则返回array_[t+1],如果小于,那么返回array_最后一个元素。

B+树

内部节点的key是其下叶子节点的最小值,

find

对于非叶子节点,

相关推荐
是小比特28 分钟前
再谈c++模板
开发语言·c++
qq_18735263431 分钟前
迭代求方程的根c++
c++·算法·迭代求方程的根c++
程序猿阿伟36 分钟前
《C++位域:在复杂数据结构中的精准驾驭与风险规避》
开发语言·数据结构·c++
lsnm38 分钟前
C++ 二叉树进阶
开发语言·c++·b树
我言秋日胜春朝★1 小时前
【C++】模版的进阶
c++
逆袭之路6661 小时前
C++17新特性探索:拥抱std::optional,让代码更优雅、更安全
c++
shuai_2582 小时前
深入解析C++单例模式:从基础到线程安全的高效实现
开发语言·c++·qt
黒井深2 小时前
Visual Studio(vs)下载安装C/C++运行环境配置和基本使用注意事项
c语言·c++·ide·visual studio
秋风起,再归来~2 小时前
C++从入门到起飞之——继承上篇 全方位剖析!
开发语言·c++·继承
大柏怎么被偷了3 小时前
【Qt】子控件选择器
开发语言·c++·qt