[CMU 15-445] #1.0 Buffer Pool_Note

Buffer pool

数据库维护一块内存, 来加快数据库对数据page的读写速度;
为了使用buffer pool须维护page table

page table

  1. 页表维护缓冲池的内容


    page table 映射到 buffer pool
  2. 对于每一个entry, 在page table中维护3个值

    1. frame id与page id的映射

    2. dirty flag: 标记该页是否被更新, 如果被更新需要刷回磁盘里

      当dirty flag有效时, 该page不应被换出, 因为它被更新了却没有被写回磁盘

    3. pin counter: 引用计数, 当还有线程使用它时不应将其写回磁盘或从缓冲池中被换出

      因为

      1. 如果线程更新了它, 还需要再次写回磁盘

      2. 如果线程使用它而它不在缓冲池中, 还需要将其换入缓冲池

  3. 必须保证线程安全, 在任何线程要操作page前必须在page table中把该项上锁(latch 轻量级锁 区别于lock)

  4. 页表无需在磁盘中持久化, 因为缓冲池本身就在易失性存储内存里, 持久化了也没用, 当系统崩溃时内存全都没了

页表pagetable与页目录page directory对比

  1. 页目录维护页的地址信息, 即page在数据库磁盘文件中的位置
  2. 页目录必须在磁盘中持久化, 因为数据库的page最终都在磁盘里, 不持久化的话系统崩溃后就不能定位page

与buffer pool相关的优化策略

1. Multiple Buffer Pools 多缓冲池

优势:

  1. 可以减少多线程查询时对latch的竞争
  2. 提高数据库局部性查找能力, 比如 对表有单独的缓冲池可以定制化查询策略

2. Pre-Fetching 预取

基于查询计划预先将未用到的page取至缓冲池中, 以提高效率


pre-fetching

比如: select * from A where val between 100 AND 250
如果用操作系统的mmap仅能做线性预取: page0, 1, 2, 3, 4, 5
而数据库使用索引, 因此预取的路线将会是 page0, 1, 3, 5, 比mmap更快

3. Scan Sharing 共享扫描游标

当能够使用独立的一个游标进行查询(把查询注册到一个游标数据管理的集合中)时, 当一个线程取得了某个page他可以通知其他线程(类似订阅/发布机制)

当其他线程需要做类似的查询时, 允许它横跨不同的线程来共享这些即时结果, 让查询搭上游标的顺风车, 具体地:

scan sharing1

Q1和Q2都需要遍历整张表, 当Q1的游标进行到page3时, Q2出现了, 此时如果没有扫描共享机制, 那么Q2需要从头开始将page0换进缓存池中(但此时page0才刚刚被换为page3), 并依次进行下去, 这将导致巨大的浪费


scan sharing2

扫描共享机制允许Q2和Q1使用同一个游标, Q2搭上这个顺风车遍历page3, 4, 5后再遍历page0, 1, 2完成对整张表的扫描, 大大减少了需要换入缓冲池的page数量

关系模型的无序性使得一些查询可以从这个机制中获得额外的优化, 比如limit 100, 只从buffer pool+随游标移动过程中中取出100项即可

4. Buffer Pool Bypass 分流缓冲池

针对一些一次性的查询, 如果将查询结果都放入buffer pool中会导致污染(即进行其他查询时不得不再换出buffer pool中的大部分页), 为了避免这种情况, 为该次查询单独申请一块临时内存, 将查询到的数据放入这块本次查询的"本地内存"中而不放入buffer pool中, 用完即销毁. 适合于一些数据量不大的join / sort操作.

buffer 替换策略

目标

  1. 要正确: 在某个数据没有被真正使用完前不应被写出或移除
  2. 求准确: 移除的page尽可能是那些未来不太会用到的page
  3. 速度快: 在page表中查找时会持有latch
  4. 体积小: 尽可能小的meta-data

方法

  1. LRU 最近最少用算法

  2. CLOCK 时钟算法

    将page围成一个圈, 每个page维护一个访问标志位, 被访问时设置为1, 时针绕圈走动, 时针走过时将标志位为1的重置为0

    当需要移除某一页时, 时针最先走到的标志位为0的page可直接移除

    这是LRU的一种近似算法, 它每次移除的并不是"最"近最少用的page, 而是在"一段时间内"未使用到的page

    在某个固定的时间窗内它与LRU是等效的, 在一些简单的点查询时效率很高

以上方法会导致sequential flooding, 即可能线性扫描大量页导致缓存污染, 解决该问题的算法:

  1. LRU-K

    将最近使用过1次的判断标准扩展为最近使用过K次, 只有最近使用过K次才会从历史队列进入缓存队列。

    核心思想是把时间戳的“绝对”变为”相对“,不是找哪个时间戳“绝对最老”,而是哪个page的“上次访问”和“上上次访问”时间戳间隔最大,它就是最近最不常用的page

  2. localization

    本地化。为某些常用的查询创建本地buffer pool,避免污染大的buffer pool

  3. priority hints

    当查询执行时,DBMS知道每一页的context,有助于DBMS自主选择替换哪一页。

    比如当自增索引存在时,数据库在进行多次ID不固定的查询时会选择替换B+树的叶子结点,而不是根节点,因为查询总是从根节点开始

其中前2种实现难度小,但大公司做的高级数据库往往使用更为复杂也更优秀的其他策略。

dirty page策略选择

  1. fast: 在需要换入某一页时, 直接找到某一clean的页移除它, 给新页腾位置, 但如果该clean页将来会被用到就将导致效率下降,这只会带来一次IO,即读入需要的页
  2. slow: 在需要换入某一页时, 写出一个dirty页, 这会直接导致两次磁盘IO,一次是写出dirty页,一次是读入想要的页

成熟的DBMS会有一条线程定时在后台做dirty页写出操作,写出了dirty页之后就可以将其标记为claen,需要替换时直接选择策略1

你可能感兴趣的:([CMU 15-445] #1.0 Buffer Pool_Note)