点击查看代码
问题:
postgresql的backend进程是通过buffer_tag访问缓冲区管理器的,那backend进程是怎么知道buffer_tag的呢?
回答:
这是一个非常好的、而且问到 PostgreSQL 内核关键路径的问题 👍
我直接按 “真正发生了什么” 的顺序来讲,避免概念跳跃。
一、先给结论(一句话版)
backend 并不是一开始就“知道” buffer_tag 的
buffer_tag 是在访问关系文件时,由 逻辑定位信息 动态计算出来的
换句话说:
👉 buffer_tag 是 backend 在“我要访问这个块”这一刻算出来的,不是查表查到的
二、buffer_tag 到底是什么?
在 PG 源码里(简化):
typedef struct BufferTag
{
RelFileNode rnode; // 关系物理文件标识
ForkNumber forkNum; // main / fsm / vm
BlockNumber blockNum;// 块号
} BufferTag;
👉 buffer_tag 唯一标识一个磁盘块
三、backend 是“怎么知道” buffer_tag 的?
核心逻辑:执行器 → 访问方法 → 缓冲区管理器
我们一步一步走。
四、从 SQL 到 buffer_tag 的完整链路
1️⃣ backend 执行 SQL
SELECT * FROM t WHERE id = 10;
backend 进程进入执行器(Executor)。
2️⃣ 执行器调用访问方法(AM)
例如:
SeqScan → heap access method
IndexScan → btree access method
此时 backend 已经知道:
Relation(表)
要访问哪个 block(BlockNumber)
这些信息从哪来?
👉 来自 表/索引的物理布局 + 扫描逻辑
3️⃣ Relation 中已经包含了 rnode
Relation 结构体里有:
RelationData
{
RelFileNode rd_node; // 就是 rnode
...
}
这个 RelFileNode 是:
表空间
数据库 OID
relfilenode
👉 backend 打开 Relation 时就已经知道 rnode
4️⃣ 扫描时算出 blockNum
比如顺序扫描:
for (block = 0; block < nblocks; block++)
{
ReadBuffer(rel, block);
}
block 是 当前正在访问的块号
这是扫描器自己维护的计数
5️⃣ forkNum 是固定的
heap 主数据:MAIN_FORKNUM
可见性映射:VISIBILITYMAP_FORKNUM
FSM:FSM_FORKNUM
扫描代码里是明确传的。
五、buffer_tag 就是在这里“拼出来的”
在 ReadBuffer() 里:
Buffer
ReadBuffer(Relation rel, BlockNumber blockNum)
{
return ReadBufferExtended(rel,
MAIN_FORKNUM,
blockNum,
RBM_NORMAL,
NULL);
}
最终在:
InitBufferTag(&tag, rel->rd_node, forkNum, blockNum);
👉 buffer_tag 是在这一刻动态构造的
六、backend 用 buffer_tag 做什么?
在 Buffer Manager 里:
backend 拿着 buffer_tag
去 共享 Buffer Hash Table 查:
命中 → 返回 Buffer
不命中 → 分配新 Buffer
建立:
buffer_tag → buffer_id
这就是 buffer cache lookup
七、关键点总结(你问的“知道”本质)
backend 并不是“事先知道 buffer_tag”
而是:
信息 来源
RelFileNode Relation 打开时确定
forkNum 访问路径决定
blockNum 扫描 / 访问算法算出来
👉 buffer_tag = backend 在访问磁盘块那一刻“现场计算”的
八、再强调一个容易误解的点
❌ 错误理解:
backend 维护了一个“buffer_tag 列表”
✅ 正确理解:
backend 只在需要访问某个块时,临时构造 buffer_tag
九、类比一下(帮助记忆)
你可以把 buffer_tag 理解成:
📦 “快递地址”
rnode = 城市 + 街道
forkNum = 主楼 / 副楼
blockNum = 门牌号
backend 并不存快递表
👉 每次要寄东西,现场写地址
十、如果你想继续深挖(我可以接着讲)
你这个问题已经在 PG 内核层 了,可以继续聊:
buffer_tag 的 hash 冲突怎么处理?
多 backend 并发访问同一 buffer_tag 的锁路径?
buffer header vs buffer descriptor?
为什么要把 forkNum 放进 tag?
local buffer 和 shared buffer 的 tag 区别?
你要哪一块,我可以直接带你走源码级路径。