05-缓冲池

本文根据CMU15-445课程内容编写,因为数据库术语较多,为避免翻译问题带来的理解偏差,部分术语使用英语表达。

1. 介绍

之前我们提到过,数据库由DBMS负责管理内存和磁盘之间的数据移动。因为数据不能直接在磁盘中进行处理,所以当上层请求数据时,DBMS首先需要将数据移动到内存中。因此,为了实现这个功能,我们需要在内存中开辟一块内存,称为缓冲池,同时需要设计一个缓冲池管理器,用于管理缓冲池。示意图如下。

这其中有一个难点在于如何减少因磁盘读写而造成的停顿。

缓冲池概览

我们可以从时间控制空间控制的角度来思考这个问题:

  • 空间角度(Spatial Control)指的是我们将页面写入磁盘的哪些位置。空间控制的目的是将大概率一起使用的页面放到磁盘上相邻的位置,来提高磁盘读取的效率。
  • 时间角度(Temporal Control)指的是什么时候将页面读入到内存中,以及什么时候将页面写回磁盘。时间控制的目的是减少因磁盘读取造成的停顿

2. 缓冲池

缓冲池是内存中一块存放页面的缓存。数据库事先向操作系统申请了一大块内存作为缓冲池,并且将缓冲池划分为一组的相同大小的页面(页面的大小和磁盘中页面一样大),其中的每一块称为frame。当上层请求某一个页面时,DBMS将磁盘中对应页面复制到缓冲池的某一个帧frame中,然后向上层返回该帧的地址。

缓冲池

缓冲池元数据(metadata)

为了维护缓冲池中的页面,我们需要一些元数据

  • 页表:首先,为了记录保存在缓冲池中的页面,我们需要一个页表。页表是一个哈希表,将页表id(page_id)映射到帧id(frame_id)。注意,这里的页表不要和存储章节的页面目录混淆。目录页保存页面id到页面在文件中物理地址的映射。

  • dirty flag:同时,对于页面,我们还要有一个dirty flag来标记该页面是否被修改。脏页在从内存中移除时需要写回磁盘。

  • pin counter:记录有多少线程在访问该页面,当pin counter不为0时,表示该页面被固定,被固定的页面不能从内存中移除。

内存分配策略

知道了缓冲池的基本结构之后,我们就需要决定怎么分配内存。一般有两种内存分配策略:

  • 全局策略(Global policy):DBMS查看所有的活跃事务,然后找到一个最优的策略来分配内存。
  • 本地策略(Local policy):查看某一个特定的事务,为该事务制定内存分配策略而不考虑其他的并发事务。

一般来说,大多数系统会采用两种方式结合的策略。

3. 缓冲池优化

为了提高数据库的运行效率,我们需要根据我们的工作负载(workload)对缓冲池进行“定制”优化,这些优化是操作系统没有办法做到的。我们有一系列缓冲池优化策略。

多缓冲池

DBMS可以根据需求维护多个缓冲池,例如,为每个事务分配一个缓冲池,为每个表分配一个缓冲池等。这么做的好处在于我们可以在每一个缓冲池上采用本地内存分配策略,减少锁的争抢,提高数据的局部性。

不过这样做就会引发一个问题,我们怎么确定需要的数据在哪个缓冲池呢?

有两种方式:

  1. 对象id(object id):为元组添加一个对象id,涉及扩展记录id,以包含关于每个缓冲区池正在管理的数据库对象的元数据。然后,通过对象标识符,可以维护从对象到特定缓冲区池的映射。
  2. 哈希:DBMS将page_id映射到对应的缓冲池。
预取

DBMS可以根据查询计划来预取页面。例如,当我们需要遍历所有的页面时,前一个页面在执行的同时,我们可以将下一个页面取到缓冲池中。这种方式在DBMS需要取多个连续页面或者使用索引查询的时候效率很高。注意:

扫描共享

扫描共享可以使查询游标重复使用数据。这个技术允许多个查询连接到同一个游标来扫描同一张表。例如,当查询开始扫描某一张表时,发现已经有一个事务在扫描这张表,那么就可以把自己的查询连接到这个正在查询的事务的游标上,跟着一起扫描这张表。由DBMS记录第二个查询是在什么时候加入扫描的,当前一个扫描结束之后,继续执行第二个查询未完成的部分。

缓冲池旁路(Buffer Pool Bypass)

扫描将不会将获取的页面存储在缓冲池中,而是保存在一个本地的内存(和缓冲池分开)。当扫描结束之后,就将这块内存中的所有数据全部丢弃,这种方式不会污染缓冲池。如果某一个操作需要读取磁盘上连续的大页序列,这种方式的效率会很高。缓冲区池旁路也可以用于临时数据(排序、连接)。

4. 操作系统页面缓存

大多数磁盘操作都是通过OS API进行操作的。除非另有明确说明,否则操作系统将维护自己的文件系统缓存

大多数DBMS使用直接I/O来绕过操作系统的缓存,以避免页面的多余副本和不得不管理不同的替换策略的情况。

Postgres是使用操作系统的页面缓存的数据库系统的一个示例。

5. 缓冲替换策略

如果缓冲池已经满了,而此时还需要从磁盘中读取新的页面时,就需要DBMS释放一个帧来为一个新页面腾出空间时,它必须决定从缓冲区池中替换哪个页面。

替换策略是DBMS实现的一种算法,它可以决定在缓冲池空间不足时从缓冲区池中删除哪个页面。

LRU(最近最少使用)

LRU替换策略记录每个页面的最后一次访问的时间戳。这个时间戳可以存储在一个单独的数据结构中,例如一个队列,以便于进行排序和提高效率。DBMS选择以驱逐具有最古老时间戳的页面。此外,页面按排序顺序保存,以减少排序时间

CLOCK(时钟算法)

时钟算法是LRU算法的一种优化。不用为每个页面维护一个时间戳,而是维护一个bit,当页面被访问时,将该bit设置为1。为了将替换操作可视化,我们将页面组织成一个带指针的圆形时钟。指针按顺序扫描,被扫过的页面如果bit为1,那么将该bit置0,继续扫描下一个页面。如果为0,那么可以删除这个页面。每一次扫描都从上次扫描结束的位置开始。

其他

LRU和CLOCK替换策略对于sequential flooding非常敏感。sequential flooding是由顺序扫描引起缓冲池内容“破坏”。因为顺序扫描会扫描表的每一个页面,因此,我们最近使用的页面可能是最不重要的页面(因为顺序扫描一般扫描每个页面只取一次),这可能会导致LRU和CLOCK替换策略的效率降低。

有三种方式来解决这个问题:

  1. 一种方式是采用LRU-K替换策略,LRU-K替换策略记录每个页面最近K次的访问时间,然后计算平均的访问时间间隔。这种方式可以预测页面的下一次访问时间。
  2. 查询本地化。DBMS会根据每个事务/查询来选择要驱逐的页面。这将尽量减少每个查询对缓冲区池的污染。
  3. 最后,优先级提示允许事务在查询执行期间根据每个页面的上下文告诉缓冲区池页面是否重要。
脏页

当我们删除页面时,对于未修改的页面,直接覆盖即可。而对于脏页,我们则必须将其修改部分持久化到磁盘(数据库持久化的要求)。

因此,当我们缓冲池内存不足时。如果选择删除“干净”的页面,那么其速度是很快的。但是如果选择删除脏页,那么我们需要等待其写入磁盘,这个操作是比较慢的。

一种解决这个问题的方法是在后台维护一个写回进程。写回进程定时扫描缓冲池中的页面,并将脏页写回。当脏页写回后,可以根据策略决定是删除该页面还是将该页面的dirty flag置0。
,这个操作是比较慢的。

一种解决这个问题的方法是在后台维护一个写回进程。写回进程定时扫描缓冲池中的页面,并将脏页写回。当脏页写回后,可以根据策略决定是删除该页面还是将该页面的dirty flag置0。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值