硬盘数据库就是数据库主体构建在硬盘上,相对的内存数据库是指数据库主体构建在内存中,两者各有优势。这里说的内存管理是指硬盘数据库的内存管理,因为内存数据库整个都在内存中其管理方式必然有着不同的设计。

一般来说,内存由操作系统管理,数据库首先通过mmap system call向操作系统获得一块较大的内存空间,然后数据库进行的内存管理就是指对这块区间的管理。为了方便,这块区域的大小一般是操作系统内存page的整数倍。

数据库都有一个buffer manager组件(不同数据库名称可能不同,但都会有一个扮演类似功能的组件)来管理控制的内存[在数据库中叫Buffer]。数据库程序向buffer manager请求一个硬盘上的block,这个block也是数据操作的最小数据单位[或者叫frame],如果这个block已经在buffer中了,那么buffer manager直接返回内存中block的地址。如果buffer中没有对应的block,那么buffer manager首先分配一个新的空间给这个block(如果有必要,剔除一些旧的block),然后从disk中找到block然后读到buffer中,放到之前开辟的区域中,并放回地址。这里的buffer manager主要负责三个任务:1. buffer中block的替换[Buffer replacement strategy], 2. 保证用户不会对崩溃的block有任何操作[Pinned block], 3. 强制输出block[Forced output of blocks]

Buffer replacement strategy

操作系统中LRU[Least Recently Used]是常用的页面调度机制,在数据库中LRU同样也是可以适用的策略。但是可能会回到一些问题,比如sequential flooding

假设数据库内存中总共有三个block大小的空间可用,如下图, 序号就是他们读入Buffer的顺序,数越大说明越是最近读入的:

Slot Block/Frame
1 A
2 B
3 C

现在数据库又要读入D,按照LRU的机制,被替换的就是A,就是

Slot Block/Frame
3 D
1 B
2 C

此时,A又要载入,因为之前为了读取D已经将A放回disk,现在姚重新读取,根据LRU被替换的是B

Slot Block/Frame
2 D
3 A
1 C

如果此时又要读入B,那么C就要被替换,可以想象如果后面再次读取C,那么又要替换D。如果一个workload正好是连续的DABCDABC,可以想想buffer manager要不断的进行I/O操作。这就是sequential flooding

这种情况下MRU[Most Recently Used]就是一种更好的方案,还是考虑相同的情况,

Slot Block/Frame
1 A
2 B
3 C

此时需要读入D,最近使用的是C,根据MRU则将C替换成D。后面依次A,B,C,AB已经在buffer中了,可以直接返回地址,读入C的之后直接将D替换。如此往复,MRU应对sequential flooding时就比LRU要好很多。

Slot Block/Frame
1 A
2 B
3 D

当然根据不同的情况和需要考虑的因素,有很多replacement策略。

Pinned Blocks

如果数据库崩溃并且需要恢复的话,这段时间内是不能让block写回disk的,所以buffer manager就是pin blocks,字面意思就是钉住这些数据不允许其进行I/O操作,知道数据库恢复。这个功能对于数据库恢复非常重要。

Forced output of blocks

有些时候即便buffer中有空余的地方,也需要将block写回disk这是不符合数据库的buffer replacement策略的,所以叫做强行输出。一个经典情况是,计算机的内存可能是volatile的[当然最近有nvm的内存出现解决了部分问题],如果计算机崩溃内存中的数据会消失,但是在disk中的数据会保留,所以有时候处于安全考虑,需要强行写回一些重要文件。