03PG缓存-写入写出

一、缓存区的写入

当后端进程想要访问一个页面时,会调用ReadBufferExtended,一般后端进程访问页面有三种情况

  • 页面在缓存池中
  • 页面不在缓存池中,从磁盘中将页面加载到缓存描述符空槽
  • 页面不在缓存池,从磁盘中将页面加载到缓存描述符受害者槽

1.读取存储在缓存池中的页面

以t1表为例,查询相关视图,得知目前此表在缓存中

test=#  SELECT * FROM buffercache('t1');
 bufferid | relfork | relblk | isdirty | usagecount | pins 
----------+---------+--------+---------+------------+------
      253 | main    |      0 | t       |          1 |    0
(1 row)

test=# explain (analyze,buffers,costs off,timing off,summary off) select * from t1;
               QUERY PLAN               
----------------------------------------
 Seq Scan on t1 (actual rows=1 loops=1)
   Buffers: shared hit=1
(2 rows)

第二行shared  hit 表示缓存命中,即从缓存中读取页面

从缓存中读取页面大致步骤如下:

  • 后台进程根据所需页面,创建页面的buffer_tag,上述例子buffer_tag为{(1634,16384,16396),0,0}
  • 以buffer_tag作为HASH键,通过HASH函数计算其对应的HASH值,根据HASH值定位到缓存中缓存表对应的HASH桶
  • 获取此HASH桶所在分区的BufMappinglock的共享锁。
  • 查找此HASH桶下的链表,找到buffer_tag为{(1634,16384,16396),0,0}的数据项,从数据项中获取buffer_id,返回给后台进程
  • 后台进程PIN住 该buffer_id对应在缓存池中的buffer,然后释放BufMappinglock
  • 获取缓存区首部锁(其实就是 32位 state 字段的1 bit,22位),将state中的18-21 位进行修改,表示usagecount 加1
  • 获取缓存内容锁共享模式,访问缓存池

2.磁盘中将页面加载到缓存描述符空槽

重启pg数据库以清空缓存

[pg14@test ~]$ pg_ctl restart -D $PGDATA -l /tmp/logfile
waiting for server to shut down.... done
server stopped
waiting for server to start.... done
server started
[pg14@test ~]$ psql -Upostgres -d test 
psql (14.6)
Type "help" for help.

test=#  SELECT * FROM buffercache('t1');
 bufferid | relfork | relblk | isdirty | usagecount | pins 
----------+---------+--------+---------+------------+------
(0 rows)

test=# explain (analyze,buffers,costs off,timing off,summary off) select * from t1;
               QUERY PLAN               
----------------------------------------
 Seq Scan on t1 (actual rows=1 loops=1)
   Buffers: shared read=1
 Planning:
   Buffers: shared hit=16 read=6
(4 rows)

第二行shared  read 表示缓存未命中,即从磁盘中读取页面

假设所需页面不在缓存池中,而且空闲链表有空槽,将页面从磁盘读取到缓存,大致步骤如下:

(1)查找缓存表

  • ​ 后台进程根据所需页面的buffer_tag,并计算其HASH值
  • ​ 以共享模式获取HASH值对应分区的BufMappingLock
  • ​ 查找缓存表,没有找到对应的Tag
  • ​ 释放BufMappingLock

(2)从freelist 中获取空缓存描述符,并将其钉住

(3)以独占模式获取相应分区的BufMappingLock

(4) 创建一条新的缓存表数据条目 ,(后台进程所需页面的buffer_tag,和钉住的空缓存描述符的buffer_id),并将其连链接对应的HASH 桶链表

(5)获取Buffer I/O锁,buffer io锁不是真正意义上的锁,只是state字段的一个标识位(26位),将所需页面从存储加载到缓存池槽中,修改,修改relcount、usagecount、io标识位(三个操作组合成一个原子操作)。原子操作完成,Buffer I/O锁也就释放了。

(6) 释放相应分区的BufMappingLock

(7)获取buffer 内容锁,访问加载到缓存池中的页面

3.磁盘中将页面加载到缓存描述符受害者槽

当PostgreSQL实例刚启动时,freelist很多,随着不断地从磁盘中缓存页面到缓存池中,freelist最终会被占满,这是就需要选已经被使用的buffer,被选中的buffer 就是“受害者”。PostgreSQL中寻找“受害者”的算法是时钟算法。

时钟算法

时钟的含义就是像时钟的指针一样,一遍一遍的遍历所有的缓存描述符。这个指针在代码的名称是nextVictimBuffer,也称为clock hand。它指向上一次受害者的下一位描述符。当后台进程需要将页面从磁盘读取到内存中,而且缓存描述符列表又没有空槽是,时钟就开始了,步骤如下:

  • 获取nextVictimBuffer指针指向的候选缓存区描述符
  • 缓存描述符为unpin:如果usage_count 为0 则该缓存区描述符对应的缓存池槽就是”受害者“,否则,usage_count 减一,并迭代至下一缓缓存区描述符
  • 缓存描述符为pin:迭代至下一缓存区描述符,并重复检查下一描述符的pin状态,直至找到受害者

usage count 初始为0,当页面第一次加入到缓存池时,对应的缓存描述符的 usage count 加一。再次被访问(查询或修改),再加一。为了避免指针循环次数太多,usage count最大值为5 。

缓存管理器将页面从磁盘加载到缓存池受害者槽步骤如下:

(1)查找缓存表没有找到

(2)时钟算法扫描缓存区描述符,找到受害者缓存区描述符,从受害者缓存表获取buffer_id,并在缓存池中将其PIN住。

(3)如果受害者是脏页,将其数据写入到磁盘

(4)以排他模式获取缓存表旧表项所在分区的BufMappingLock,

(5) 获取新表项所在分区的将新表项BufMappingLock,插入到此缓存表的链表

(6)将旧表项从缓存表链表中删除,释放旧表项的BufMappingLock。

(7) 将目标页数据从存储加载至受害者槽位,用受害者槽位的buffer_id 更新描述符的标识位,将脏位设置为0,。

(8) 释放缓存表上新表项所在分区的BufMappingLock

(9) 访问对应的缓存区槽位

二、缓存区的写出

当脏页被选定为受害者时,需要将缓存区的脏页写入存储,写出步骤如下:

  • 获取对应缓存描述符上的共享内容锁,并buffer IO标识位设为1。
  • 根据具体情况,调用XlogFlush()函数,将WAL缓存区上的WAL数据写入到当前的WAL段文件,
  • 将脏页数据写到磁盘中
  • 更新buffer IO 标识位为0,并释放缓存描述符上的内容锁
  • 23
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南風_入弦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值