持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
缓存池
引言
我们之前已经学习过基于硬盘的数据库的数据流动方式,如下图所示,数据都存储在硬盘中。但是用户读取数据时直接访问的硬盘的时间代价太大,因此需要在内存空间中开辟出一片缓存池(Buffer Pool) 区域用于将硬盘的数据放在内存中方便存取。
缓冲池负责将物理页从主内存来回移动到磁盘。它允许DBMS支持大于系统可用内存量的数据库,缓存池的操作对于系统其它部分来说是透明的,例如,系统通过标识符访问缓存池中的页,系统本身并不知道这个页的数据是在内存中还是在磁盘中。
一般缓存池首先加载文件中的page directory,再根据执行引擎发送来的指令在文件中提取数据
内存和硬盘中的page directory总是同步的。
这一节的内容都是以页为单位了,设计的不同让人觉得这一节和上一节的内容有点割裂
Buffer Pool Management
physical structural
缓存池内的存储单元和磁盘中是一样的,页大小就是帧大小
page table和buffer pool是什么关系,缓存池一般比较大,有的可以达到几G,那么大的缓存池中有哪些页是需要专门的索引机制来支查询的,这里的机制就是page table。
page table是缓存池的页索引,page directory是磁盘页的索引
optimization
有很多方法可以优化缓冲池,使其适应应用程序的workload。
multiple buffer pools
在很多DBMS中,并不是只有一个缓存池,而是对于每一个分类数据(元数据、索引、数据page)都有一个缓存池
如上图所示,这样做的好处是减少并发竞争和、提升数据的空间性。多缓存池实现的方式有两种,一在数据的索引里面嵌入缓存池ID,二另一种方法是索引时对页面id进行散列,以选择要访问的缓冲池。
pre-fetching
具体 pre-fetching怎么实现呢
DBMS还可以通过基于查询计划预取页面进行优化。然后,在处理第一组页面时,可以将第二组页面预取到缓冲池中。DBMS在按顺序访问多个页面时通常使用这种方法。
例如在以树作为page索引的DBMS,例如PostgreSQL用B树,MySQL用B+树。对page的查询到第二层时,第三层的page就已经能够知道预先可以载入哪些page了。在这里是page3和page5。
根据底层数据结构不同,pre-fetching的实现各有不同,这里不做解释。
scan sharing
当查询语句有相似的扫描路径时,可以将多个查询路径合并为同一个,共用缓存池的数据。例如下面的Q1和Q2,都是全表扫描,因此Q2可以在Q1执行中间进行合并。
可以看到不少数据库是支持scan sharing的,但是也有不支持的,因为scan sharing有时会因为检测扫描路径是否重合而增加很多开销。对于线性查找很少的数据库就没必要支持这一特性了。
buffer pool bypass
有时执行器扫描的数据只需要用到一次,不需要缓冲,例如顺序扫描算子不会将获取的页面存储在缓冲池中,以避免开销。这种方法用于临时数据(sorting、join)很有效。
注:操作系统的文件系统对于磁盘page也是有缓存的,但是DBMS一般会绕过操作系统来进行缓存,因为DBMS总是比OS在缓存上做的更好。
Buffer Replacement Policies
当DBMS需要释放一个框架来为新页面腾出空间时,它必须决定从缓冲池中移出哪个页面,这种移除需要保证正确性、准确性、速度和元数据开销小。因此需要很多特殊的策略。
LRU替换策略
和操作系统中的LRU概念一样,LRU会维护每个页面上次访问的时间戳。该时间戳可以存储在单独的数据结构中,例如队列,以便进行排序并提高效率。DBMS选择退出具有最早时间戳的页面。此外,页面按排序顺序保存,以减少逐出时的排序时间。但是实现上这种算法开销大到无法负担。
CLOCK替换策略
时钟策略是LRU的近似解,不需要每页单独的时间戳。在时钟策略中,每个页面都有一个参考位。访问页面时,设置为1。扫描时,检查页面位是否设置为1。如果是,则设置为零,如果不是,则将其逐出。
如上图所示,page1之前被访问过,所以ref是1,指针指到page1后将ref置为0,之后指到page0,发现page0没有被访问过,所以将page0替换出去。
LRU和CLOCK策略有不小的问题:最明显的是在sequential flooding大批量线性扫描的时候,LRU和CLOCK没有任何作用,只能不断替换之前的页面,换句话说,最近使用的页面实际上是最不需要的页面,以最近时间作为replacement的凭证有些片面。
LRU-K策略
一种解决方案是LRU-K,它以时间戳的形式跟踪每个page最后K个引用的历史,并计算后续访问之间的间隔。这样的话更多引用的page对比其它的page有更高的优先性。
Localization
另一个优化是每个查询的局部化,DBMS根据每个事务/查询选择要逐出的页面。例如一个查询语句在执行时只替换和该语句有关的page,也就是说,单个查询计划中使用过的缓存之后大概是不会用的。
Priority hints
最有效的是priority hints,我们可以通过设定某些页很高的优先级来保证这些常用的页一直在内存中。例如以树为结构存储page的DBMS中,根节点的page是永远在内存缓冲池中的。
Dirty Pages
DBMS对脏页的处理和DBMS类似,一种较慢的方法是将脏页写回磁盘,以确保其更改被持久化(存在硬盘里)。另一种方法是DBMS可以定期遍历页表,并将脏页写入磁盘。这样只会造成定期的卡顿,而总体速度提升。注意这一种方法可能造成数据在持久化之前就关机丢失,因此DBMS在修改数据后会至少写入一条日志在硬盘中,这一条日志保证了修改的数据能够写进硬盘中。
其它Buffer Pool
DBMS并不是只有存储数据需要缓存,还有例如下图的缓存
这些其他内存池可能并不总是由磁盘支持,具体取决于实现。
缓存技术在 DBMS中应用比想象中的要广
\