本篇博客是对CMU15-445课程project1的总结,代码的实现借鉴了一些其他前辈的代码,因为自己的C++实在是小白水平,对数据库的实现理解的也不够透彻,就抄了大家的代码,然后理解这些代码之后,再作此篇总结,加深对知识点的理解。
如果本文有纰漏,请多指教。
这里附上借鉴的博客:[已满分]CMU数据库(15-445)实验1-BufferPoolManager。
本次实验的原文要求在这里PROJECT #1 - BUFFER POOL
关于本门课程的知识点总结我推荐[CMU-15445]
总述
Project1要求我们实现一个数据库的缓存池(Buffer Pool),其中,涉及的相关组件(类)主要有:
- LRU Replacer
- Buffer Pool Manager
- Disk Manager
- Page
一些相关概念包括:
- Page(Page Table)
- Frame(Buffer Pool)
- Pin/Unpin
- Latch/Lock
我认为,在开始总结Buffer Pool的各个模块功能之前,有必要先归纳一些各个概念的区别,我自己在写代码的时候,经常弄混这些概念。
概念
Page
有关Page的概念,在《数据库系统概念》(以下简称《概念》)中给出的是,
一个**块(block)是一个逻辑单元,它包含固定数目的连续扇区。块大小在512字节到几KB之间。数据在磁盘和主存储器之间以块为单位传输。术语页(page)**常用来指块,尽管在有些语境(例如闪存)中指的是另外的含义。
在《数据库系统实现》(以下简称《实现》)中给出的是,
磁盘被划分称磁盘块(或就称为块,或者像操作系统称为页),每块的大小是4~64KB。整个块被从一个成为缓冲区的连续内存区域中移进移出。
可以认为,块,页,是对同一概念的不同叫法,取决于场景不同。其表述的概念,都是磁盘上某一柱面上的连续扇区(固定数目),数据在磁盘和缓冲区(内存)之间传输,传输的单位就是块(页)。
Page ID
此外,表示一个页,使用的是Page ID。Page ID是一个逻辑概念,由于数据库中的数据本质上是磁盘上存储的文件,因此,物理上Page ID应该映射到某个文件的某个偏移量上,这种映射是通过某种间接层(indirection layer)实现的。
Frame
Frame称为帧。Frame其实和Page在逻辑上指的是同一个概念,只不过,当Page被读取到缓冲区中,它就换了个名字——帧,它同样也有Frame ID(和Page ID相同)。课程课件中提到
内存区是以定长的页数组的形式组织的,其中每一个数组条目,被称为一个帧(frame)。
当DBMS请求一个页时,被请求页的一份拷贝就被读取进其中一个帧中。
Page Table
至于Page Table,叫做页表。页表是一个字典(映射)结构,它是缓冲区管理器的一个辅助结构。页表的功能,是跟踪磁盘页在缓冲区中的位置,也就是说,它是一个Page ID到Frame ID的映射。
从这一点上来说,Page ID是磁盘页的属性,不同的磁盘页的Page ID不同,当磁盘页被读取进缓冲区时,该磁盘页会在缓冲区中有一个位置,即Frame ID。缓冲区是Frame的定长数组,一共只有buffer_pool_size
大小的frame,所以,当磁盘页读进写出,会存在不同的磁盘页先后出现在同一个帧位置的情况,那么这个Page Table,就是保存当前页ID到帧ID的映射。
Pin/Unpin
Pin,叫做钉住(Unpin就是解钉吧~)。它指的是钉住/解钉某个块(或者说是帧,因为这里钉住和解钉是内存区的概念,操作对象应该叫做帧)。为什么要钉住一个块?要知道,我们的DBMS在对磁盘文件进行读入写出时,必须要保证线程的安全,当一个块被读取的同时,块又被修改,这会威胁到线程安全。借用《概念》中的说法:
为了使数据库系统能够从系统崩溃中恢复,限制一个块写回磁盘的时间是十分必要的。例如:当一个块上的更新操作正在进行时,大多数恢复系统不允许将该块写回磁盘。不允许写回磁盘的块被称为被钉住(pinned)的块。尽管很多操作系统不提供对被钉住的块的支持,但是这个特性对可从崩溃中恢复的数据库系统十分重要。
Latch/Lock
Latch和Lock都是数据库中的锁,前者叫闩,后者叫锁,目的都差不多。区别,课程课件中是这么给出的:
Locks:
- 保护数据库的逻辑内容不被其他事务破坏,如元组、表、数据库。
- 事务期间持有。
- 需要能够回滚(rollback)变化。
Latch
- 保护DBMS内部数据结构的关键部分不受其他线程的影响。
- 操作期间持有。
- 不需要能够回滚变化。
说白了,这里latch是在代码层面,保护数据结构不受线程影响;lock是在逻辑层面,保护逻辑对象(元组、表、数据库)不受事务影响。在本次实验的编码中,每个类都有一个自己的latch,在C++中使用mutex
实现,通过使用互斥量,达到避免多线程操作混乱的目的。
LRU Replacer
Replacer的作用,是提供一个缓冲区替换策略(buffer replacement strategy),《概念》中提到:
当缓冲区中没有剩余空间时,在新块读入缓冲区之前,必须把一个块从缓冲区移出。多数操作系统使用**最近最少使用(Least Recently Used)**策略,即最近访问最少的块被写回磁盘,并从缓冲区移走。这种简单的方法可以加以改进以用于数据库应用。
本次实验中采取的策略正是LRU(最基础的版本,LRU有很多扩展改进版本)。重点是,LRU只生效于缓冲区没有剩余空间时,如果有剩余空间,那么就直接选用一个空帧,来放置读入的页即可。
在实验代码中,要求我们实现如下几个函数,具体功能见实验要求说明,不