PostgreSQL两阶段提交事务源码分析
源码版本:PG 13.3
PG 支持两阶段提交事务(2PC),本文基于 PG 13.3 源码,简单分析 2PC 事务处理相关的逻辑。
1. 两阶段事务提交的处理过程
2PC 各阶段的命令调用主要包含如下三个:
- prepare trancation xxx
- commit prepared xxx
- rollback prepared xxx
prepare transaction xxx 命令表示让名称为 xxx 的事务就绪,此时事务并不可见,其主要是调用如下两个函数:
- StartPrepare() 函数,把事务相关的数据全部存储到内存变量 static struct xllist records。
- EndPrepare() 函数,把 StartPrepare() 函数中保存到 records 变量中的数据写入 wal。
看一个例子,prepare transaction 'T200' 的调用堆栈:
exec_simple_query PortalRun PortalRunMulti PortalRunUtility ProcessUtility standard_ProcessUtility PrepareTransactionBlock //设置gid,此处为T200 finish_xact_command CommitTransactionCommand PrepareTransaction StartPrepare EndPrepare
commit prepared 和 rollback prepared 命令用于将已经就绪(prepared)的 2PC 事务提交或者回滚,两者最终都是调用 FinishPreparedTransaction() 函数,通过参数 isCommit 来区别是提交还是回滚操作。
举个例子 commit prepared 'T200' 的调用堆栈,如下:
exec_simple_query PortalRun PortalRunMulti PortalRunUtility ProcessUtility standard_ProcessUtility FinishPreparedTransaction RecordTransactionCommitPrepared //提交 RecordTransactionAbortPrepared //回滚
- RecordTransactionCommitPrepared 函数的主要逻辑就是将事务相关的数据写到 wal 日志,然后在 clog 中将 xid 对应的事务状态设置为 commited。
- RecordTransactionAbortPrepared 函数的主要逻辑是将事务 abort 相关的数据写入 wal 日志,然后在 clog 中将 xid 对应的事务状态设置为 aborted。
2. 两阶段事务相关数据结构
两阶段事务相关的信息会存储在 TwoPhaseState 指向的共享内存里,该变量定义如下:
static TwoPhaseStateData *TwoPhaseState;
- TwoPhaseShmemSize() 函数计算需要的共享内存大小。
- TwoPhaseShmemInit() 函数初始化共享内存。
每一个 prepared 的事务对应一个 GlobalTransactionData 类型的元素,它包含事务号 xid,gid 等信息,它的详细定义如下:
typedef struct GlobalTransactionData { GlobalTransaction next; /* list link for free list */ int pgprocno; /* ID of associated dummy PGPROC */ BackendId dummyBackendId; /* similar to backend id for backends */ TimestampTz prepared_at; /* time of preparation */ /* * Note that we need to keep track of two LSNs for each GXACT. We keep * track of the start LSN because this is the address we must use to read * state data back from WAL when committing a prepared GXACT. We keep * track of the end LSN because that is the LSN we need to wait for prior * to commit. */ XLogRecPtr prepare_start_lsn; /* XLOG offset of prepare record start */ XLogRecPtr prepare_end_lsn; /* XLOG offset of prepare record end */ TransactionId xid; /* The GXACT id */ Oid owner; /* ID of user that executed the xact */ BackendId locking_backend; /* backend currently working on the xact */ bool valid; /* true if PGPROC entry is in proc array */ bool ondisk; /* true if prepare state file is on disk */ bool inredo; /* true if entry was added via xlog_redo */ char gid[GIDSIZE]; /* The GID assigned to the prepared xact */ } GlobalTransactionData;
TwoPhaseStateData 结构相当于是申请的共享内存的头,在共享内存内部会通过链表把 GlobalTransactionData 组织起来,通过 TwoPhaseStateData.freeGXacts 快速找到一个可用的 GlobalTransactionData 元素。TwoPhaseStateData 结构的详细定义如下:
typedef struct TwoPhaseStateData { /* Head of linked list of free GlobalTransactionData structs */ GlobalTransaction freeGXacts; /* Number of valid prepXacts entries. */ int numPrepXacts; /* There are max_prepared_xacts items in this array */ GlobalTransaction prepXacts[FLEXIBLE_ARRAY_MEMBER]; } TwoPhaseStateData;
GlobalTransactionData.pgprocno 成员与 PreparedXactProcs.pgprocno 对应,PreparedXactProcs 在 InitProcGlobal() 函数中初始化,初始化 ProcGlobal->allProcs 的时候,为 2PC 事务预留了 max_prepared_xacts 个数量的 PGPROC 元素。因此可以理解为每个 prepared 的 2PC 事务,它都有一个对应的 backend 结构 PGPROC 与之对应,直到该 prepared 的事务提交或者回滚才会释放 PGPROC。max_prepared_xacts 参数不能动态修改,需要重启才能生效。
在计算 PGPROC 总数时,将 max_prepared_xacts 计算在内,如下:
uint32 TotalProcs = MaxBackends + NUM_AUXILIARY_PROCS + max_prepared_xacts;
PreparedXactProcs 指向为 2PC 预留的 PGPROC 元素,如下:
PreparedXactProcs = &procs[MaxBackends + NUM_AUXILIARY_PROCS];
3. 两阶段事务的持久化
prepared 的事务会在两个地方进行持久化,第一个是 wal 日志,第二个是 pg_twophase 目录,每个 xid 对应一个文件。
对于 prepared 的事务,应对崩溃恢复的场景同样是写入 wal 日志。在 prepare transaction 时写 wal 日志,记录 gxact->prepare_start_lsn 和 gxact->prepare_end_lsn,崩溃恢复时能够从 wal 日志里面恢复 2PC 事务相关的信息。通过 pg_waldump 可以解析 wal 日志,看到 2PC 事务相关的 wal record 信息。
在执行 checkpoint 之后,2PC 事务信息会被写入 pg_twophase 目录下的文件,事务最终提交或者回滚,pg_twophase 目录中的信息将被清除。pg_twophase 目录下的文件格式为 "pg_twophase/xid",xid 为 16 进制格式,每一个事务号对应一个文件,文件末尾有 crc32 校验。具体的文件格式如下:
- xl_xact_prepare,别名 TwoPhaseFileHeader
- gid,字符串,比如 T100,长度由 hdr.gidlen 记录
- subxids,数组,大小由 hdr.nsubxacts 记录
- RelFileNode 数组,表示事务提交要删除的文件,大小由 hdr->ncommitrels 记录
- RelFileNode 数组,表示事务回滚要删除的文件,大小由 hdr->nabortrels 记录
- SharedInvalidationMessage 数组,大小由 hdr->ninvalmsgs 记录
- 其他,详见函数 StartPrepare()
4. 两阶段事务相关函数
- restoreTwoPhaseData(),恢复二阶段事务信息,从 pg_twophase 目录中遍历所有文件,将文件中的内容加载到共享内存中。
- CheckPointTwoPhase(),二阶段事务 checkpoint 逻辑,遍历 TwoPhaseState->prepXacts 数组,从 wal 日志里面读取 gxact 对应的 wal 记录,将记录写到 pg_twophase 目录下的文件中。RecreateTwoPhaseFile() 函数用于将二阶段事务信息写入文件中。执行完成后,gxact->ondisk 设置为 true。
- XlogReadTwoPhaseData() 从 wal 日志里读取指定 lsn 开始的记录,该记录必须是一个有效 2PC 的 wal 记录。
2PC 事务 checkpoint 操作函数调用关系如下:
CreateCheckPoint CheckPointGuts CheckPointTwoPhase XlogReadTwoPhaseData //从wal日志中读取2PC事务数据 RecreateTwoPhaseFile //将2PC事务数据写入pg_twophase目录下的文件中
5. 两阶段事务相关 GUC 参数
max_prepared_transactions,默认值为 0,表示禁用 2PC 事务,取值范围 0 ~ 0x3FFFF,不能动态修改。
文章评论