缓冲管理器管理共享内存和持久存储之间的数据传输,并可能对 DBMS 的性能产生重大影响。
postgresql中缓冲区管理器,存储和后端进程之间的关系:
概览
缓冲区管理器结构
PostgreSQL缓冲区管理器包括一个缓冲区表、缓冲区描述符和缓冲池,这些将在下一节中介绍。缓冲池存储数据文件的页, 如表和索引,以及自由空间映射和可见性映射的页面。缓冲池是一个数组,数据的每个插槽存储数据文件的一页。缓冲池数组的索引称为buffer_id。
缓冲区标签
在PostgreSQL中,可以为所有数据文件的每个页面分配一个唯一的标记,即缓冲区标签。当缓冲区管理器收到请求时, PostgreSQL使用目标页面的缓冲区标签。
其中,关系文件节点用于定位页面所属的关系,关系分支编号用于定位关系文件的具体分支文件,页面块号则在具体分支文件中指明相应页面的偏移量。
缓冲区标签由三个值组成,分别是关系文件节点、关系分支编号和页面块号。第一个值分别代表了表空间、数据库和表的oid;第二个值代表关系表的分支号;一个关系可能有三种分支,分别是关系主体(main分支,编号为0)、空闲空间映射( fsm分支,编号为1)及可见性映射(vm分支,编号为2)。最后一个值代表页面号。
例如,{(16821, 16384, 37721), 0, 7} 标签表示,在某个表空间(oid=16821)中,某个数据库(oid=16384)的某张表(oid=37721)的 0 号分支( 0代表关系表本体)的第 7 号页面。再比如,缓冲区标签 {(16821, 16384, 37721), 1, 3} 表示该表空闲空间映射文件的三号页面。关系本体 main 分支编号为 0,空闲空间映射 fsm 分支编号为1。
后端进程如何读取数据页
步骤如下图:
(1)读取表或索引页时,后端进程会向缓冲区管理器发送包含该页的buffer_tag的请求。
(2)缓冲区管理器返回存储请求页的插槽的buffer_ID。如果请求的页未存储在缓冲池中,则缓冲管理器将页从持久存储加载到缓冲池插槽中,然后返回buffer_ID的插槽。
(3)后端进程访问buffer_ID的插槽(读取所需的页面)。
当后端进程修改缓冲池中的页面(例如向页面插入插入tuples)时,尚未刷新到存储的修改后的页面称为脏页。
页面替换算法
当所有缓冲池插槽都被占用,但请求的页面未被存储时,缓冲管理器必须在缓冲池中选择一个将被请求的页面替换的页面。通常,在计算机科学领域,页面选择算法称为页面替换算法,所选页面称为牺牲者页面。
自计算机科学出现以来,对页面替换算法的研究一直在进行;因此,以前提出了许多替换算法。从8.1版开始,PostgreSQL使用了clock sweep算法,因为它比以前版本中使用的LRU 算法更简单、更高效。
脏页刷盘
脏页最终应刷新到持久性存储,不过,缓冲区管理器需要其他的协助来执行此任务。在PostgreSQL中,两个后台进程 (checkpoint和backgroud writer) 负责此任务。
缓冲区管理器的结构
PG 的缓冲区管理器由三层组成,即缓冲表层、缓冲区描述符层和缓冲池层。如下图所示。
-
缓冲表层是一个散列表,它存储着页面的 buffer_tag 与描述符的 buffer_id 之间的映射关系。
-
缓冲区描述符层是一个由缓冲区描述符组成的数组。每个描述符与缓冲池槽一一对应,并保存着相应槽的元数据。
-
缓冲池层是一个数组。每个槽都存储一个数据文件页,数组槽的索引称为 buffer_id。
缓冲表
缓冲表层的作用是,根据后端进程发送的请求,创建目标页面的 buffer_tag(这个结构下面会讲,暂时将其理解为缓冲区中包含的一个磁盘块),然后将 buffer_tag 通过内置的散列函数映射到哈希桶槽,并分配 buffer_id, 即目标页面在缓冲池数组中存储的槽位的序号。
缓冲表在逻辑上可分为三部分: 散列函数、散列桶槽及数据项。为了避免哈希函数的冲突的发送,缓冲表采用了使用链表的分离链接方法来解决冲突。当数据项被映射至同一个桶槽时,该方法会将这些数据项保存在一个链表中。缓冲表层结构如下图所示:
数据项包括两个值,即页面的 buffer_tag 和包含页面元数据的描述符的 buffer_id。例如,数据项 Tag_A,id=1 表示,在 buffer_id=1 对应的缓冲区描述符中,存储着页面 Tag_A 的元数据。
缓冲区描述符
缓冲区描述符保存着页面的元数据,对应的页面则保存在缓冲池的槽位中。缓冲区描述符的结构由BufferDesc 结构定义。
/* src/include/storage/buf_internals.h (9.6版本之后, 移除了一些字段) */
/* 缓冲区描述符的标记位定义(9.6版本之后)
* 注意,TAG_VALID实际上意味着缓冲区散列表中有一条与本tag关联的项目
*/
#define BM_LOCKED (1U << 22) /* 缓冲区首部被锁定 */
#define BM_DIRTY (1U << 23) /* 数据需要写入 */
#define BM_VALID (1U << 24) /* 数据有效 */
#define BM_TAG_VALID