普通缓冲式关系文件读写中,PostgreSQL 的共享缓冲位于操作系统文件缓存之上。命中共享缓冲时,不需要再次通过下面的文件读取路径;没有命中时,也可能由操作系统直接从内存提供数据。同一文件内容可以在两层各有副本,所以这里并不承诺数据库与内核共用同一份物理内存,更不能把两层缓存理解为完全没有重复占用。
坏掉的样子
执行计划里的共享读取计数,表示块被读入 PostgreSQL 缓冲池,不证明存储设备实际执行了一次物理读取。如果把每次共享读取都算成磁盘未命中,已经温热的操作系统缓存也会被误认为冰冷,从而引出错误的内存配置判断。同样,第二次查询更快,也不能独自说明究竟是哪一层缓存发生了变化,或是否只是其他执行条件改变。
缓存驻留是临时状态。其他查询可能挤走页面,工作集超过容量时,也不能指望所有页永久留在内存里;关系扫描采用的缓冲策略还可能影响复用方式。因此应使用已知大小的工作集、检查实际访问路径,并把耗时结论与真正观察到的缓存层事实分开。没有设备层测量,就不能仅凭一个计划字段宣布磁盘速度发生了变化。
快速验证
使用 PostgreSQL 18 可丢弃实例和超级用户,确保附带的缓冲观察与预热扩展可安装、且尚未安装。本轮在 PostgreSQL 18.6 单用户后端重跑 SQL,使用一百二十八兆字节共享缓冲;未测试 psql 传输与并发活动。下面的数据可以轻松放下。通过一个全新的 psql -X -v ON_ERROR_STOP=1 会话按顺序执行。关系驱逐函数用于开发测试,这里只针对隔离实验创建的对象操作:
CREATE SCHEMA cache_layer_demo;
CREATE EXTENSION pg_buffercache WITH SCHEMA cache_layer_demo;
CREATE EXTENSION pg_prewarm WITH SCHEMA cache_layer_demo;
CREATE TABLE cache_layer_demo.items (id integer PRIMARY KEY,payload text)
WITH (autovacuum_enabled=false);
INSERT INTO cache_layer_demo.items
SELECT g,repeat(md5(g::text),10) FROM generate_series(1,12000) AS g;
VACUUM (ANALYZE) cache_layer_demo.items;
CREATE VIEW cache_layer_demo.residency AS
SELECT count(*) AS heap_buffers FROM cache_layer_demo.pg_buffercache
WHERE reldatabase=(SELECT oid FROM pg_database WHERE datname=current_database())
AND relfilenode=pg_relation_filenode('cache_layer_demo.items')
AND relforknumber=0;
SET max_parallel_workers_per_gather=0;
SELECT pg_relation_size('cache_layer_demo.items')/current_setting('block_size')::int AS heap_blocks;
SELECT heap_buffers>0 AS initially_buffered FROM cache_layer_demo.residency;
本次构建的主堆有五百四十六个块,初始缓存比较返回真。观察视图限制数据库、文件标识与主分支,并假定这个新建实验只使用默认表空间。通用监控还必须区分表空间,因为文件编号不是数据库内跨表空间唯一的物理身份。每个缓冲条目的观察是一致的,但整个视图并不是同一瞬间的原子缓存快照。接着,仅驱逐这个关系的共享缓冲:
SELECT * FROM cache_layer_demo.pg_buffercache_evict_relation('cache_layer_demo.items'::regclass);
SELECT heap_buffers=0 AS no_shared_heap_buffers FROM cache_layer_demo.residency;
函数返回驱逐、刷出和未能驱逐的数量,本次分别为五百五十、五百五十和零。总数包含辅助分支,所以比只统计主堆的块数更大。后面的比较确认主堆共享缓冲已为空,但这个结果没有回答操作系统里是否仍然缓存着同一批文件页面。两层的状态需要分别观察,不能从一层为空推断另一层也为空。
通过操作系统同步读取主堆,但不把内容加载进数据库共享缓冲:
SELECT cache_layer_demo.pg_prewarm('cache_layer_demo.items','read','main');
SELECT heap_buffers=0 AS still_no_shared_heap_buffers FROM cache_layer_demo.residency;
预热函数处理了五百四十六个块,共享缓冲数量仍是零。这里选择的是同步读取模式;若改用缓冲模式,就会把页加载到数据库缓冲池。现在执行一个需要访问全部载荷的查询:
EXPLAIN (ANALYZE,BUFFERS,COSTS OFF,TIMING OFF,SUMMARY OFF,FORMAT JSON)
SELECT sum(length(payload)) FROM cache_layer_demo.items;
顺序扫描报告五百四十六次共享读取,共享命中为零。这与操作系统缓存已经温热完全相容,不能把它翻译成五百四十六次设备读操作。再执行同样的查询:
EXPLAIN (ANALYZE,BUFFERS,COSTS OFF,TIMING OFF,SUMMARY OFF,FORMAT JSON)
SELECT sum(length(payload)) FROM cache_layer_demo.items;
第二次扫描共享读取为零,共享命中为五百四十六。两次都处理一万两千行,所以缓冲计数变化并不是因为查询少读了业务记录。最后核对驻留状态和聚合结果,再清理实验与扩展:
SELECT heap_buffers>0 AS now_shared FROM cache_layer_demo.residency;
SELECT sum(length(payload)) FROM cache_layer_demo.items;
RESET max_parallel_workers_per_gather;
DROP SCHEMA cache_layer_demo CASCADE;
载荷字符总数应为三百八十四万,共享驻留比较为真。精确块数取决于行布局和构建参数;关键观察是同步预热后共享缓冲仍为空,首次查询发生共享读取,再次查询转为命中。这些结果没有测量设备延迟,也不保证某个固定倍数的加速。用它们说明缓存层次是合适的,用来预测生产查询耗时则证据不足。
为整个服务器分配内存
资源配置文档对内存充足的专用服务器,给出了共享缓冲约占系统内存四分之一的起始建议,但这不是任何负载都最优的常数。连接本身、并发排序与哈希、维护任务,以及操作系统缓存,都在使用同一个主机预算。只增加一层的容量,可能把其他工作所需的余量挤掉,因此不能孤立地看共享命中率决定配置。
修改之前应测量有代表性的工作集与并发请求。全部缓存已经温热的微型测试会隐藏读写压力,完全冷启动测试又可能夸大平时高度复用页面的负载代价。把缓存层计数、设备层观察和查询结果结合起来,才能把配置调整的收益归到它实际改变的那一部分,而不是把所有内存读取都叫作磁盘优化。
来源
PostgreSQL 18 资源消耗,共享缓冲与操作系统缓存的内存预算,已核验 2026-10-15。
PostgreSQL 18 pg_prewarm,同步读取和数据库缓冲预热模式,已核验 2026-10-15。
PostgreSQL 18 pg_buffercache,缓冲身份及开发测试用的关系驱逐函数,已核验 2026-10-15。
PostgreSQL 文件布局,数据库、表空间与关系文件身份,已核验 2026-10-15。