You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL堆元组插入时BlockNumber获取的线程安全问题

PostgreSQL插入流程中的线程安全与关系扩展疑问解答

前置背景

执行以下SQL会在PostgreSQL中插入一条堆元组:

CREATE TABLE my_table (
  id SERIAL PRIMARY KEY,
  value varchar(20)
);
INSERT into my_table(id, value) VALUES (1,'hey')

插入的大致步骤为:

  1. 获取ROW EXCLUSIVE LOCK
  2. 若relcache未缓存,则从系统目录表获取Relation Data
  3. 选择合适的BlockNumber用于插入
  4. 将数据写入对应Page

其中步骤3在非批量插入(BulkInsertState为false)时,通过RelationGetTargetBlock方法获取目标块,其定义如下:

#define RelationGetTargetBlock(relation) \
    ( (relation)->rd_smgr != NULL ? (relation)->rd_smgr->smgr_targblock : InvalidBlockNumber )

问题1:RelationGetTargetBlock非线程安全,多写入者无锁更新targblock会导致什么问题?

PostgreSQL默认采用多进程架构,每个数据库连接对应独立的后端进程,进程间内存空间完全隔离,RelationData和SMgrRelationData是进程本地缓存,常规场景下不存在跨进程的并发修改问题。但如果是同一进程内的多线程场景(如自定义扩展使用线程),无锁操作会引发以下问题:

  • 竞态条件导致重复块选择:多个线程同时读取smgr_targblock,其中一个线程更新目标块后,其他线程仍基于旧值尝试插入已满的块,触发块空间检查失败,需要重新选择块,增加不必要的开销。
  • 更新丢失:并发修改smgr_targblock时,一个线程的更新可能被另一个线程覆盖,导致后续插入持续尝试写入错误的块,需要多次重试才能找到可用块。

问题2:多写入场景下,RelationSetTargetBlock是否存在线程安全问题?

  • 常规多进程场景下,每个后端进程独立维护自己的smgr_targblock,进程间无共享内存修改,因此不存在线程安全问题。
  • 同一进程内的多线程场景中,RelationSetTargetBlock直接修改SMgrRelationData的smgr_targblock字段,未加锁时会出现更新丢失的竞态问题:多个线程同时修改该字段,后续线程的修改会覆盖之前的结果,导致目标块记录混乱,插入操作频繁重试或选择错误块。

问题3:多写入时获取过时目标块,是否会导致关系持续被过度扩展?

会出现短暂的过度扩展,但不会持续发生,原因如下:

  1. 当进程尝试插入到过时的目标块时,会先检查块剩余空间,发现已满后调用RelationAddBlocks扩展新块。高并发场景下,多个进程可能同时执行此操作,导致多个空块被创建。
  2. 但RelationGetBufferForTuple方法在目标块不可用时,会重新扫描关系的最后几个块寻找可用空间,后续插入会优先使用这些已创建的空块,不会持续扩展新块。
  3. 这种过度扩展仅出现在高并发插入的峰值阶段,后续VACUUM操作可以清理未使用的空块(表未被锁定为不可截断状态时)。

内容的提问来源于stack exchange,提问作者scameeer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:37:10