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

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

原创 内核开发者 2024-11-25
371

如何在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 --写入文件

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论