熟肉视频地址:
今天的课程是关于期待已久的缓冲池的话题,其实就是 DBMS 如何管理它的内存并从磁盘来回移动数据,我们希望DBMS自己来管理这些内存与磁盘存储交换的操作,而不是把它留给操作系统。你可以从两个方面考虑数据库存储和内存管理问题:
第一个是空间控制,也就是我们从物理上考虑在磁盘上写页的位置,我们要把页面存储在磁盘的什么地方,以达到最大的收益。我们的目标是让页保持在一起,如果有一些页经常被我们的应用程序同时访问我们把它们连续地放在磁盘上。这么做的原因是顺序访问磁盘比随机访问消耗小得多野快得多。
我们需要考虑的第二个方面是时间控制。这意味着当我们从磁盘取页到内存时,我们希望 DBMS 能够以一种最小化磁盘 I/O 的方式来实现这一点:如果有一个您需要访问的页,而它目前不在内存中,那么就需要从磁盘读取,会有一个等待页从磁盘载入内存的 I/O 阻塞,我们想尽量避免这些,也就是 DBMS 需要找到一种有效的方法,将在同一时间被访问的页面以最少的 I/O 次数同时保存在内存中。
我们这样做的主要原因也是因为相对于访问内存,直接访问磁盘的耗时大太多了,是不可以接受的。所以,作为 DBMS 工程师,找出一种有效的方法来维护这个缓冲池,尽可能地将数据保存在内存中,这对我们来说是非常重要的
到目前为止,我们一直在讨论磁盘上面的数据库文件,这些文件被分成了很多页,并且有目录页,它存储从页 id 到文件中的物理位置或偏移量的映射。在磁盘文件上面有我们的缓冲池(Buffer Pool),它为执行引擎(Execution Engine)服务
例如:我们有一个执行引擎发出一个请求访问第二页,缓冲池中没有第二页,缓冲池要做的是,首先将文件目录加载到内存中,找出第二页的物理位置,然后获取它,这样我们就能返回一个内存中的第二页的指针给执行引擎。
以上是整个缓冲池如何工作的一个大概的例子,具体来说,我们今天这节课要讲的主题还是关于缓冲池的高级概念:
特别是缓冲池管理器(Buffer Pool Manager),即软件中负责管理缓冲池的部分,我们会看一下缓冲池管理器使用的不同算法。包括替换策略,如何决定哪些页要读取到内存,哪些页要从内存中删除,最后我们会看一些其他类型的可能存在于 DBMS 中的内存池。
缓冲池的结构是一个固定大小的页的数组,每一个数组条目都被称为一个帧(Frame):它是磁盘上的数据库文件的页的大小,这样我们就可以把磁盘上的页映射到缓冲池的数组槽中。当 DBMS 请求一个页时,我们要做的就是将页复制到缓冲池中的这些帧中。
实际上,我们现在还需要一个间接层才能访问这些页,即通过页表(Page Table)
页表实际上记录了存储在内存中的页的映射,类似于数据库磁盘文件的文件头的槽页。页目录记录页在磁盘上的位置,页表则是会记录页的布局,以及它们在内存缓冲池中的位置。这里我们有从第一页和第三页到缓冲池中的帧的映射,页表还将负责维护关于每个页的一些额外元数据,例如:
- dirty 标记:是一个布尔值,告诉我们页在加载到内存后是否被修改过。
- pin 标记或者引用计数:如果我们想要一个还会被使用的页留在缓冲池的内存中,我们不希望它被删除,我们可以用 pin 标记这一页。或者通过记录引用计数让我们知道哪些页还在被查询使用。
- Latch锁存器:如果我们有一堆并发的查询,我们有多个线程或查询都访问试图修改这个页表,一般需要在页表的一个位置设置一个锁存器,来防止并发修改。
这里我们需要理清一个重要的概念区别,即锁(Lock)与锁存器(Latch)
在数据库世界中的锁与锁存器,与操作系统中的锁与锁存器的概念是不一样的。在数据库的世界中:
- 锁(Lock):指的是对于数据库的逻辑抽象的保护,例如锁的可以是整张表,也可以是索引,也可以是元组,这些都是与DBMS相关的逻辑抽象。锁通常在事务期间获取并保持,并且要考虑事务回滚
- 锁存器(Latch):我们通常指的是保护某些底层关键部分的短暂的锁存器,比如保护一个内部数据结构或者数据库管理系统中发生的修改,我们不需要能够回滚这些改变。有点类似于 Mutex(互斥锁)
下一个我们想搞清楚的是页目录(Page Directory)与页表(Page Table)的区别:
页目录就是从页id到页物理位置的映射,它需要被持久化,这样就算重启我们也可以加载以便追踪我们可能需要的各个页。
页表在内存中,它是临时的。我们不需要持久化这个页表,页表可以在我们执行查询时逐步建立。
一个问题:在内存中设置了页表某一帧的 dirty 位后,如果掉电,我们会丢失对页面的更新吗?会的,如果在缓冲池中有一些页被设置了脏位,这意味着它们被一些查询修改了,它们还没有持久化到磁盘上。但是后面我们会讨论到事务保证,如果你有一个事务,那么这个事务直到所有的更改都以某种方式(后面我们会知道通过一种类似于写入提前写日志(WAL,Write Ahead Log)的方式)持久化到磁盘上之前才会提交完成,以保证事务的完整性不受宕机影响。
我们如何决定哪些页会存在于我们的缓冲池中?一般有两种策略:
- 全局策略(Global Policies):根据系统中在同一时间并发运行的所有查询进行综合考虑
- 本地策略(Local Policies):基于每个查询来加载和移除页,但是也会有页的共享