Buffer 管理
Buffer 管理简述
什么是 Buffer
Buffer 就是 DBMS 在 主存中临时存储数据 并 与磁盘进行数据交互 的空间。CPU 只能操作主存,而非易失性DBMS数据存储在磁盘,因此必须将数据加载到主存,才可以被执行,所以Buffer就是主存中临时存储这些数据的空间。
特别注意的是,这里的 Buffer 虽然是主存空间中的一块区域,但其职责是与磁盘进行数据交互,并非和 Sort Buffer / Join Buffer 等属于单纯的内存区域。简单来说 Sort Buffer 、Join Buffer、Buffer Pool 、Log Buffer 等属于内存空间中的区域,但与磁盘进行数据交互却只有 Buffer Pool 和 Log Buffer 。
DBMS 中 Buffer 管理实际上就是管理的 与磁盘进行数据交互 的 主存空间 =>Buffer Pool/Log Buffer。
如何使用 Buffer
DBMS 所使用的 Buffer 可以是操作系统所提供的 Buffer ,也可以是 DBMS 自己申请的 Buffer。
- 通过文件的 Read 和 Write 可以使用操作系统的 Buffer 和 Cache,而 Buffer 和 Cache 也属于 Buffer,只是这个Buffer是由操作系统所管理;(绝对没有DBMS使用这种方式)
- 通过 MMAP 可以使用操作系统的Direct Buffer,而 Direct Buffer 也属于Buffer,只是这个Buffer也是由操作系统所管理。与上一条实现不同的是,MMAP申请的Buffer减少了用户态和内核态之间的拷贝。(非常少的DBMS使用这种方式,因为减轻了DBMS的复杂度)
- DBMS 从主存中申请一块单独的 Buffer,这个Buffer是由DBMS所管理。(大多数DBMS的实现方式,核心也是基于MMAP实现)
什么是 Buffer 管理
对于通过使用MMAP和文件Open而得到的 Buffer 受操作系统所管理,这种方式不需要考虑。此处的Buffer管理特指,管理 DBMS 自己申请的 Buffer。
由于 Buffer 是DBMS自己申请的,那么文件的加载、脏页的刷新、页回收等都需要 DBMS 自己实现。这就是 DBMS 的 Buffer 管理。
为什么 DBMS 需要自己进行 Buffer
- 页大小灵活可控;数据页和 Buffer 页尽可能一致。
-
- 利用文件系统Buffer和Cache时,页大小遵循操作系统的固定4k,当数据页超过4K时,会导致内存页中一页不能加载到完整的数据页,当发生页面置换时,会使得一部分数据置换回去,内存中的数据将会非常混乱。
- 利用MMAP时也会产生上条类似的问题。
- DBMS自己实现Buffer管理时,定义每一页大小等于数据页,由于DBMS每次操作单元都是一个数据页,因此将内存和磁盘统一起来了。数据不再混乱。
- Buffer 持久化时机可控;
-
- 利用文件系统Buffer和Cache时,buffer和cache遵循操作系统的持久化策略,每个一段时间刷新一次。这会导致数据页的部分数据被持久化。(目前WAL能避免此问题)
- 利用MMAP时,Buffer也是遵循操作系统的持久化策略。
- DBMS自己实现Buffer管理时,可以控制
- OS 缺页中断导致的 IO 停顿问题;
-
- 操作系统的内存管理中,缺页中断时,那么会进行磁盘IO进行加载,而此时用户线程处于阻塞状态。
- DBMS 自己实现 Buffer 管理时,可以通过预加载尽可能的避免此问题。
- 避免磁盘IO;只有DBMS自己才懂DBMS的数据。
-
- 对于操作系统管理的内存来说,所有的页都是一样的;
- 对于DBMS自己管理的内存来说,页与页之间是不一样的,不同的数据页可能有不同的行为逻辑。
stackoverflow.com/questions/3…
Buffer 种类
DBMS 定义多个Buffer,即为了职责隔离,也为了提升性能。
Buffer Pool
核心Buffer,主要用于存储数据页、索引页、Undo页等。
Log Buffer
用于存储Redo Log数据,目的是实现WAL技术。从而利用WAL实现系统故障时的恢复性。
Buffer 页面管理
页请求
脏页选择策略
当Buffer页被修改后,Buffer 管理标记当前页为脏页,通常DBMS维护一个脏页链表,新的脏页被追加到尾部,而在脏页刷新时,将链表中的所有脏页都持久化。
脏页刷新时机
在以下场景下数据脏页会被刷新:
- 每经过一段时间;
- Redo Log Buffer 满时;
- Buffer Pool 空间不足时;
- DBMS 进程正常结束时;
注意:
根据WAL,脏页刷新前必须写入到日志。这意味着日志的写入很耗费性能。
因此实际DBMS实现中,通常使用Log Buffer作为缓冲,缺点是可能出现Buffer Poll的数据先于Log Buffer被持久化,而优化方案是,当事务提交时强制刷新Log Buffer到磁盘,这样就保证了,提交成功的事务日志一定被持久化了(正确应该反过来说:只有日志被持久化成功,那么才算是事务提交成功)。
页置换
当Buffer空间不够时,发生页面置换,通常使用LRU/Clock算法。
预加载
根据查询计划预测的提前加载一些数据页,如查询计划中需要范围性的数据,而此时就可以根据查询计划预先将一些数据页加载到Buffer Poll中。
DBMS 性能抖动且磁盘IO占用
- 是否出现缺页中断;
- 是否出现Buffer Pool频繁的刷新;
- 是否Redo Log Buffer太小导致Buffer Pool刷新;
Buffer 优化
- Multi Buffer;避免Buffer之间读写竞争,职责隔离。
- 尽可能大的Buffer Poll;尽可能大的空间能避免内存不够时的频繁页面置换。
- 数据页预加载;