暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

聊聊PG中的两阶段提交和pg_twophase

PolarDB 2025-05-09
184

聊聊PG中的两阶段提交和pg_twophase

如何在PG中使用两阶段提交(2PC)

我们用一个例子来解释下2PC的用处和影响

  1. 修改max_prepared_transactions配置>0

  2. 建表

create table test (id int);

  1. 启动事务,插入数据,然后prepare txn
begin;
insert into test values(1);
prepare transaction 'test_2pc';

  1. 此时虽然事务没有显式commit或rollback,但插入的数据已经完成第一阶段提交

  2. 重启数据库

  3. 可以发现pg_twophase文件夹下有内容,就是对应prepare transaction的产物

  4. 查询test表,并没有直接插入的数据 1

  5. 完成第二阶段

commit prepared 'test_2pc';

  1. 查询test表,可以看到有之前insert的数据1,同时也可以看到pg_twophase下的内容已经清空

总结一下,2PC共有三个接口

1. prepare transaction 'xxx'
2. commit prepared 'xxx'
3. rollback prepared 'xxx'

2PC存在的意义和使用注意事项

  1. 可以看到2PC是跨越事务和session存在的,在session A中完成第一阶段,保存数据,在session B中可以完成第二阶段,真正提交数据

  2. PG的2PC接口是一种基础设施,可以被用来构建分布式PG数据库,比如citus在处理跨节点的数据时,就会在事务提交时,用2PC的prepare transaction 和 commit prepared 接口来保证数据的原子性

  3. 2PC带来的影响:尽管2PC可以帮助我们实现某些特定的需求,但从运维角度看,还是需要尽量减少未完成的2PC事务,让2PC事务尽快提交或回滚。如果长时间存在未完成第二阶段的2PC事务,数据库会认为这个事务是一个active的事务,同时这个2PC也可能会持有锁等资源,会影响数据库vacuum,导致数据膨胀,也会造成xid耗尽等问题,影响数据库性能和可用性。

pg_twophase与2PC

  1. pg_twophase这个文件夹里是什么内容

文件夹中会出现名如 000002E9 的文件,文件内容由下面的内容构成

/*
 * 2PC state file format:
 *
 * 1. TwoPhaseFileHeader
 * 2. TransactionId[] (subtransactions)
 * 3. RelFileNode[] (files to be deleted at commit)
 * 4. RelFileNode[] (files to be deleted at abort)
 * 5. SharedInvalidationMessage[] (inval messages to be sent at commit)
 * 6. TwoPhaseRecordOnDisk
 * 7. ...
 * 8. TwoPhaseRecordOnDisk (end sentinel, rmid == TWOPHASE_RM_END_ID)
 * 9. checksum (CRC-32C)
 *
 * Each segment except the final checksum is MAXALIGN'd.
 */

其中 TwoPhaseFileHeader 中存放的是一些元数据信息

typedef struct xl_xact_prepare
{
uint32 magic; /* format identifier */
uint32 total_len; /* actual file length */
TransactionId xid; /* original transaction XID */
Oid database; /* OID of database it was in */
TimestampTz prepared_at; /* time of preparation */
Oid owner; /* user running the transaction */
int32 nsubxacts; /* number of following subxact XIDs */
int32 ncommitrels; /* number of delete-on-commit rels */
int32 nabortrels; /* number of delete-on-abort rels */
int32 ninvalmsgs; /* number of cache invalidation messages */
bool initfileinval; /* does relcache init file need invalidation? */
uint16 gidlen; /* length of the GID - GID follows the header */
XLogRecPtr origin_lsn; /* lsn of this record at origin node */
TimestampTz origin_timestamp; /* time of prepare at origin node */
} xl_xact_prepare;

这里文件名其实是 TransactionId xid; 的十六进制表示。

  1. 内核是怎么使用 pg_twophase 文件的

pg_twophase主要用于持久化未完成的2PC事务信息,在数据库启动等场景下,内核会调用

restoreTwoPhaseData

-> ProcessTwoPhaseBuffer

-> ReadTwoPhaseFile

函数从磁盘将文件读取解析。 而在内存中,每一个 prepared 的事务对应一个 GlobalTransactionData 类型的元素,GlobalTransactionData 存在于共享内存中,这也是为什么最开始要设置 max_prepared_transactions 的原因。

  1. 为什么要有pg_twophase文件夹、为什么pg_twophase大部分时候并没有内容

prepare transaction 执行完成时,就会在redo log中记录下这个只完成第一阶段提交事务的信息。而这部分数据在第二阶段提交时,还会用到,所以需要一直保存到第二阶段完成或回滚。

而在checkpoint的时候,redo log可能会被删除,所以需要把这部分信息单独保存到pg_twophase中。所以可以观察到,在上面例子中,重启db前,pg_twophase下是空的,重启后,才会有 000002E9 这样的文件存在。

那么,checkpoint是如何进行持久化pg_twophase的?具体可以看这些函数的实现

CreateCheckPoint

-> CheckPointGuts

-> CheckPointTwoPhase

-> XlogReadTwoPhaseData --从xlog读取相关内容

-> RecreateTwoPhaseFile --写入文件


文章转载自PolarDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论