内容简介:铃木启修 | 作者期待您的加入,一起成长
铃木启修 | 作者
PostgreSQL 已获得 DB-Engines 排行榜 2017 年和2018年的“年度数据库”称号,发展如此迅猛,它究竟有什么内幕呢?接下来,我们将选择PostgreSQL重要的子系统之一缓冲区管理器展开介绍,探讨它的工作原理。
缓冲区管理器结构
PostgreSQL 缓冲区管理器 非常高效,它 管理着共享内存和持久存储之间的数据传输,对于数据库管理系统的性能有着重要的影响。它由缓冲表、缓冲区描述符和缓冲池组成。缓冲表层是一个散列表,它存储着页面 buffer_tag 与描述符 buffer_id 之间的映射关系。缓冲区描述符层是一个由缓冲区描述符组成的数组。每个描述符与缓冲池槽一一对应,并保存着相应槽的元数据。请注意,术语“缓冲区描述符层”只是在本章中为方便起见而使用的术语。缓冲池层是一个数组。每个槽都存储一个数据文件页,数组槽的索引称为 buffer_id 。缓冲区管理器的三层结构如图 1 所示。
图 1 缓冲区管理器的三层结构
缓冲区管理器的工作原理
当后端进程想要访问所需页面时,它会调用 ReadBufferExtended 函数。
函数 ReadBufferExtended 的行为因场景而异,在逻辑上具体可以分为三种情况。
-
访问存储在缓冲池中的页面
当从缓冲池槽中的页面里读取行时, PostgreSQL 进程获取相应缓冲区描述符的共享 content_lock ,因而缓冲池槽可以同时被多个进程读取。
当向页面插入(及更新、删除)行时,该 postgres 后端进程获取相应缓冲区描述符的独占 content_lock (注意,这里必须将相应页面的脏位置设为 "1" )。
访问完页面后,相应缓冲区描述符的引用计数值减 1 。
图 2 是访问存储在缓冲池中的页面示意图。
图 2 访问存储在缓冲池中的页面
我们来介绍最简单的情况,即所需页面已经存储在缓冲池中。在这种情况下,缓冲区管理器会执行以下步骤:
-
创建所需页面的 buffer_tag (在本例中 buffer_tag 是 'Tag_C' ),并使用散列函数计算与描述符相对应的散列桶槽。
-
获取相应散列桶槽分区上的 BufMappingLock 共享锁。
-
查找标签为 'Tag_C' 的条目,并从条目中获取 buffer_id 。本例中 buffer_id 为 2 。
-
将 buffer_id=2 的缓冲区描述符钉住,即将描述符的 refcount 和 usage_count 增加 1 。
-
释放 BufMappingLock 。
-
访问 buffer_id=2 的缓冲池槽。
-
将页面从存储加载到空槽
图 3 是将页面从存储加载到空槽的示意图。
图 3 将页面从存储加载到空槽
在第二种情况下,假设所需页面不在缓冲池中,且 freelist 中有空闲元素(空描述符)。这时,缓冲区管理器将执行以下步骤:
-
( 查找缓冲区表(本节假设页面不存在,找不到对应页面)。
第一,创建所需页面的 buffer_tag (本例中 buffer_tag 为 'Tag_E' )并计算其散列桶槽。
第二,以共享模式获取相应分区上的 BufMappingLock 。
第三,查找缓冲区表(根据假设,这里没找到)。
第四,释放 BufMappingLock 。
-
从 freelist 中获取空缓冲区描述符,并将其钉住。在本例中所获的描述符: buffer_id=4 。
-
以独占模式获取相应分区的 BufMappingLock (此锁将在步骤( 6 )中被释放)。
-
创建一条新的缓冲表数据项: buffer_tag='Tag_E’, buffer_id=4 ,并将其插入缓冲区表中。
-
将页面数据从存储加载至 buffer_id=4 的缓冲池槽中,如下所示:
第一,以排他模式获取相应描述符的 io_in_progress_lock 。
第二,将相应描述符的 IO_IN_PROGRESS 标记位设置为 1 ,以防其他进程访问。
第三,将所需的页面数据从存储加载到缓冲池插槽中。
第四,更改相应描述符的状态,将 IO_IN_PROGRESS 标记位设置为 "0" ,且 VALID 标记位设置为 "1" 。
第五,释放 io_in_progress_lock 。
-
释放相应分区的 BufMappingLock 。
-
访问 buffer_id=4 的缓冲池槽。
-
将页面从存储加载到受害者缓冲池槽
在这种情况下,假设所有缓冲池槽位都被页面占用,且未存储所需的页面。图 4 是将页面从存储加载到受害者缓冲池槽的示意图。
图 4 将页面从存储加载到受害者缓冲池槽
缓冲区管理器将执行以下步骤:
-
创建所需页面的 buffer_tag 并查找缓冲表。在本例中假设 buffer_tag 是 'Tag_M' (且相应的页面在缓冲区中找不到)。
-
使用时钟扫描算法选择一个受害者缓冲池槽位,从缓冲表中获取包含着受害者槽位 buffer_id 的旧表项,并在缓冲区描述符层将受害者槽位的缓冲区描述符钉住。本例中受害者槽的 buffer_id=5 ,旧表项为 Tag_F,id = 5 。时钟扫描将在下一节介绍。
-
如果受害者页面是脏页,则将其刷盘( write & fsync ),否则进入步骤 4 。
在使用新数据覆盖脏页之前,必须将脏页写入存储中。脏页的刷盘步骤如下:
第一,获取 buffer_id=5 描述符上的共享 content_lock 和独占 io_in_progress_lock 。
第二,更改相应描述符的状态:相应 IO_IN_PROCESS 设置为 1 , JUST_DIRTIED 位设置为 0 。
第三,根据具体情况,调用 XLogFlush() 函数将 WAL 缓冲区上的 WAL 数据写入当前 WAL 段文件 。
第四,将受害者页面的数据刷盘至存储中。
第五,更改相应描述符的状态;将 IO_IN_PROCESS 位设置为 "0" ,将 VALID 位设置为 "1" 。
第六,释放 io_in_progress_lock 和 content_lock 。
-
以排他模式获取缓冲区表中旧表项所在分区上的 BufMappingLock 。
-
获取新表项所在分区上的 BufMappingLock ,并将新表项插入缓冲表:
第一,首先需要创建一个全新的表项:由 buffer_tag='Tag_M' 与受害者的 buffer_id 组成的新表项。
第二,以独占模式获取新表项所在分区上的 BufMappingLock 。
第三,将新表项插入缓冲区表中。
-
从缓冲表中删除旧表项,并释放旧表项所在分区的 BufMappingLock 。
-
将目标页面数据从存储加载至受害者槽位,然后用 buffer_id=5 更新描述符的标识字段,将脏位设置为 0 ,并按流程初始化其他标记位。
-
释放新表项所在分区上的 BufMappingLock 。
-
访问 buffer_id=5 对应的缓冲区槽位。
本文节选自博文视 点新书《PostgreSQL指南:内幕探索》。 基于发挥最大效能的先天动力, 数据库学习者对其内部运行或者实现机制有着本能的兴趣。好在, PostgreSQL 也并未设计成黑盒子。深入了解其机制后,开发人员可以进行高效的应用设计,写出高性能的 SQL 语句;运维人员可以针对性地进行性能优化,快速对问题进行分析、定位和解决。如果,举世能找到的唯一秘辛, 现在 就静静躲在 阅读原文 后面,你会不点开吗?
内容简介 : 本书介绍PostgreSQL内部的工作原理,包括数据库对象的逻辑组织与物理实现,进程与内存的架构,并依次剖析几个重要子系统——查询处理、外部数据包装器、并发控制、清理过程、缓冲区管理、WAL、备份及流复制。本书为 DBA 与系统开发者提供一幅全景概念地图,有助于读者形成对数据库实现的整体认识,亦可作为 PostgreSQL源代码 深入学习的导读手册,对于理解数据库原理与PostgreSQL内部实现大有裨益。本书适合数据库开发人员及相关领域的研究人员、数据库DBA及高等院校相关专业的学生阅读。
新书出版
→
期待您的加入,一起成长
以上所述就是小编给大家介绍的《解读年度数据库PostgreSQL:如何巧妙地实现缓冲区管理器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective java 中文版(第2版)
Joshua Bloch / 俞黎敏 / 机械工业出版社 / 2009-1-1 / 52.00元
本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。 本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。一起来看看 《Effective java 中文版(第2版)》 这本书的介绍吧!
Base64 编码/解码
Base64 编码/解码
HEX CMYK 转换工具
HEX CMYK 互转工具