postgresql源码学习(五)—— 提交事务
结束事务分为两类:
- 提交:CommitTransaction
- 回滚(包含清理):AbortTransaction
由于比较长,我们分开两篇记录。
一、 CommitTransaction
1. gdb测试
会话1,隐式事务会自动提交
会话2
跟之前一样,我们先只是过一遍这个函数流程,里面具体调用函数的步骤这里都会先跳过,避免深陷泥潭。Lets Go!
2. 具体代码与跟踪
调用栈如下
/** CommitTransaction** 注意:如果想修改此函数逻辑,最好同时也修改 PrepareTransaction 函数*/
static void
CommitTransaction(void)
{TransactionState s = CurrentTransactionState; //事务栈TransactionId latestXid;bool is_parallel_worker;is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS);
我看到这有点疑惑,之前的文章不是说隐式事务不会有INPROGRESS的状态吗?于是又回去翻了一下,噢,原来是隐式事务没有TBLOCK_INPROGRESS状态(事务块状态而不是事务状态)。这俩蛮容易弄混的,需要注意。postgresql源码学习(二)—— 事务块状态转换_Hehuyi_In的博客-CSDN博客
/* 如果是并行worker提交,强制进入并行模式. */if (is_parallel_worker)EnterParallelMode();ShowTransactionState("CommitTransaction");
/** 检查当前事务状态*/if (s->state != TRANS_INPROGRESS)elog(WARNING, "CommitTransaction while in %s state",TransStateAsString(s->state));Assert(s->parent == NULL);/** Do pre-commit processing that involves calling user-defined code, such* as triggers. SECURITY_RESTRICTED_OPERATION contexts must not queue an* action that would run here, because that would bypass the sandbox.* Since closing cursors could queue trigger actions, triggers could open* cursors, etc, we have to keep looping until there's nothing left to do.
* 执行涉及调用用户定义代码(如触发器)的预提交处理。
* 因为关闭游标可能会执行触发器,触发器可能打开游标等等,所以我们必须一直循环,直到没有什么可做的。*/for (;;){/** 触发所有after触发器*/AfterTriggerFireDeferred();/** Close open portals (converting holdable ones into static portals).* If there weren't any, we are done ... otherwise loop back to check* if they queued deferred triggers. Lather, rinse, repeat.
* 关闭打开的portals(将holdable portals转换为static portals),portals好像是一种内存资源?
* 循环检查触发器队列直至全部关闭完*/if (!PreCommit_Portals(false))break;}
/** The remaining actions cannot call any user-defined code, so it's safe* to start shutting down within-transaction services. But note that most* of this stuff could still throw an error, which would switch us into* the transaction-abort path.* 剩余的操作不能调用用户自定义的代码,因此可以安全地开始关闭事务内的服务。* 但是请注意,大多数这些动作仍然会抛出错误,这会导致流程切换到事务中止的路径上。*/CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT: XACT_EVENT_PRE_COMMIT);
/* 如果有并行workers, 需要清理 */if (IsInParallelMode())AtEOXact_Parallel(true);/* Shut down the deferred-trigger manager,关闭延迟触发器管理器 */AfterTriggerEndXact(true);/** Let ON COMMIT management do its thing (must happen after closing* cursors, to avoid dangling-reference problems)
* 由ON COMMIT管理器执行(必须在关闭游标后执行,避免挂起引用问题)*/PreCommit_on_commit_actions();/** Synchronize files that are created and not WAL-logged during this* transaction. This must happen before AtEOXact_RelationMap(), so that we* don't see committed-but-broken files after a crash.
* 同步该事务创建的没有WAL日志记录的文件。这步必须在AtEOXact_RelationMap()前执行,避免在crash后我们看不到已提交但是broken了的文件*/smgrDoPendingSyncs(true, is_parallel_worker);/* close large objects before lower-level cleanup ,在低级别清理前关闭大对象 */AtEOXact_LargeObject(true);/** Insert notifications sent by NOTIFY commands into the queue. This* should be late in the pre-commit sequence to minimize time spent* holding the notify-insertion lock. However, this could result in* creating a snapshot, so we must do it before serializable cleanup.
* 将NOTIFY命令发送的通知插入到队列中。这应该在预提交序列的末尾,以最小化持有notify-insertion锁的时间。然而这可能导致创建一个快照,因此必须在序列化清理前执行这步。*/PreCommit_Notify();
/** Mark serializable transaction as complete for predicate locking* purposes. This should be done as late as we can put it and still allow* errors to be raised for failure patterns found at commit. This is not* appropriate in a parallel worker however, because we aren't committing* the leader's transaction and its serializable state will live on.*/if (!is_parallel_worker)PreCommit_CheckForSerializationFailure();/* Prevent cancel/die interrupt while cleaning up,清理期间禁用中断,避免被打断 */HOLD_INTERRUPTS();/* Commit updates to the relation map --- do this as late as possible,提交更新到relation map -- 尽量晚地执行该动作 */AtEOXact_RelationMap(true, is_parallel_worker);
注意这里有特别重要的两步:
- 设置事务状态为已提交 s->state = TRANS_COMMIT;
- 将事务日志写回磁盘 RecordTransactionCommit(),关于这步,以后还要继续深入学习
/** 设置事务状态*/s->state = TRANS_COMMIT;s->parallelModeLevel = 0;if (!is_parallel_worker){/** We need to mark our XIDs as committed in pg_xact. This is where we* durably commit.将已提交的xid保存在pg_xact中(事务日志写回磁盘),这是我们持久化提交的位置*/latestXid = RecordTransactionCommit();}
else{/** We must not mark our XID committed; the parallel leader is* responsible for that.并行worker则不需要标记,由并行leader处理*/latestXid = InvalidTransactionId;/** Make sure the leader will know about any WAL we wrote before it* commits.确保leader在提交之前知道worker写入的WAL*/ParallelWorkerReportLastRecEnd(XactLastRecEnd);}TRACE_POSTGRESQL_TRANSACTION_COMMIT(MyProc->lxid);/** Let others know about no transaction in progress by me. Note that this* must be done _before_ releasing locks we hold and _after_* RecordTransactionCommit.
* 通知其他进程,本进程中已没有进行中的事务。* 注意,这必须在释放持有的锁之前、RecordTransactionCommit之后执行。*/ProcArrayEndTransaction(MyProc, latestXid);/** This is all post-commit cleanup. Note that if an error is raised here,* it's too late to abort the transaction. This should be just* noncritical resource releasing.
* 这些都是提交后清理。
* 请注意,如果这里才出现错误终止事务就太迟了,这应该是非关键的资源释放。* The ordering of operations is not entirely random. The idea is:* release resources visible to other backends (eg, files, buffer pins);* then release locks; then release backend-local resources. We want to* release locks at the point where any backend waiting for us will see* our transaction as being fully cleaned up.
* 操作的顺序并不是完全随机的。
* 其思想是:先释放对其他后台进程可见的资源(如文件、buffer pins),然后释放锁,最后释放后端本地资源。
* 我们希望在所有等待本后台进程看到本事务被完全清理时才释放锁。* Resources that can be associated with individual queries are handled by* the ResourceOwner mechanism. The other calls here are for backend-wide* state.
* 与单个查询关联的资源由ResourceOwner机制处理。
* 这里的其他调用是针对后台进程范围状态的。*/CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT: XACT_EVENT_COMMIT);ResourceOwnerRelease(TopTransactionResourceOwner,RESOURCE_RELEASE_BEFORE_LOCKS,true, true);
/* Check we've released all buffer pins,检查所有已释放的buffer pins */AtEOXact_Buffers(true);/* Clean up the relation cache,清理关系缓存 */AtEOXact_RelationCache(true);/** Make catalog changes visible to all backends. This has to happen after* relcache references are dropped (see comments for* AtEOXact_RelationCache), but before locks are released (if anyone is* waiting for lock on a relation we've modified, we want them to know* about the catalog change before they start using the relation).
* 使目录更改对所有后台进程可见。
* 这必须发生在relcache引用被删除之后(参见AtEOXact_RelationCache注释),
* 但在锁被释放之前(如果有人在等待我们修改了的表的锁,我们希望他们在开始使用该表前知道目录的更改)。*/AtEOXact_Inval(true);AtEOXact_MultiXact();ResourceOwnerRelease(TopTransactionResourceOwner,RESOURCE_RELEASE_LOCKS,true, true);ResourceOwnerRelease(TopTransactionResourceOwner,RESOURCE_RELEASE_AFTER_LOCKS,true, true);
/** Likewise, dropping of files deleted during the transaction is best done* after releasing relcache and buffer pins. (This is not strictly* necessary during commit, since such pins should have been released* already, but this ordering is definitely critical during abort.) Since* this may take many seconds, also delay until after releasing locks.* Other backends will observe the attendant catalog changes and not* attempt to access affected files.
* 同样,在事务期间删除的文件的清理最好在释放relcache和buffer pin之后进行。(这在提交过程中并不是必须的,因为这样的pins应该已经被释放了,但是该顺序在中止过程中绝对是至关重要的。)
* 因为这可能需要较长的时间,所以也要延迟到释放锁之后。
* 其他后台进程将监控相关的catalog更改,不尝试访问受影响的文件。*/smgrDoPendingDeletes(true);AtCommit_Notify();//一大波资源清理AtEOXact_GUC(true, 1);AtEOXact_SPI(true);AtEOXact_Enum();AtEOXact_on_commit_actions(true);AtEOXact_Namespace(true, is_parallel_worker);AtEOXact_SMgr();AtEOXact_Files(true);AtEOXact_ComboCid();AtEOXact_HashTables(true);AtEOXact_PgStat(true, is_parallel_worker);AtEOXact_Snapshot(true, false);AtEOXact_ApplyLauncher(true);pgstat_report_xact_timestamp(0);CurrentResourceOwner = NULL;ResourceOwnerDelete(TopTransactionResourceOwner);s->curTransactionOwner = NULL;CurTransactionResourceOwner = NULL;TopTransactionResourceOwner = NULL;AtCommit_Memory();
// 重置事务栈变量s->fullTransactionId = InvalidFullTransactionId;s->subTransactionId = InvalidSubTransactionId;s->nestingLevel = 0;s->gucNestLevel = 0;s->childXids = NULL;s->nChildXids = 0;s->maxChildXids = 0;XactTopFullTransactionId = InvalidFullTransactionId;nParallelCurrentXids = 0;
/** done with commit processing, set current transaction state back to default,完成事务提交后,将当前事务状态改回TRANS_DEFAULT*/s->state = TRANS_DEFAULT;RESUME_INTERRUPTS(); //恢复中断
}
3. 主要流程图
二、 事务日志写回磁盘
上面流程图里最重要的是事务日志写回磁盘部分:RecordTransactionCommit(),保证已提交数据不会丢失。
if ((wrote_xlog && markXidCommitted &&synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||forceSyncCommit || nrels > 0) //判断是否要求同步提交,nrels > 0暂时没懂{XLogFlush(XactLastRecEnd); //如果是,则必须先写日志/** Now we may update the CLOG, if we wrote a COMMIT record above。更新CLOG,更新事务状态*/if (markXidCommitted)TransactionIdCommitTree(xid, nchildren, children);}else //如果是异步提交,不要求先写WAL,但崩溃时可能有数据丢失{/*设置异步提交最新的LSN */XLogSetAsyncXactLSN(XactLastRecEnd);/** 将最新的LSN保存到异步提交的事务组中,不要求先写WAL即可提交事务,同时将事务状态保存到CLOG中*/if (markXidCommitted)TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);}
RecordTransactionCommit函数内的主要调用流程如下,本篇我们暂时不详细分析了
参考:
《PostgreSQL技术内幕:事务处理深度探索》第1章
《PostgreSQL数据库内核分析》第7章
PostgreSQL 源码解读(122)- MVCC#7(提交事务-整体流程)_ITPUB博客
PostgreSQL 源码解读(123)- MVCC#8(提交事务-实际提交过程)_ITPUB博客
postgresql源码学习(五)—— 提交事务相关推荐
- PostgreSQL源码学习(1)--PG13代码结构
PostgreSQL源码学习(1)–PG13代码结构 PostgreSQL代码结构 Bootstrap:用于支持Bootstrap运行模式,该模式主要用来创建初始的模板数据库. Main:主程序模块, ...
- PostgreSQL源码学习(一)编译安装与GDB入门
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 PostgreSQL源码学习(一)编译安装与GDB入门 前言 一.安装PostgreSQL 1.获取源码 2.配置 3.编译 3.安装 ...
- postgresql源码学习(27)—— 事务日志⑦-日志落盘上层函数 XLogFlush
一. 预备知识 1. XLOG什么时候需要落盘 事务commit之前 log buffer被覆盖之前 后台进程定期落盘 2. 两个核心结构体 这两个结构体定义代码在xlog.c,它们在日志落盘过程中非 ...
- postgresql源码学习(51)—— 提交日志CLOG 原理 用途 管理函数
一. CLOG是什么 CLOG(commit log)记录事务的最终状态. 物理上,是$PGDATA/pg_xact目录下的一些文件 逻辑上,是一个数组,下标为事务id,值为事务最终状态 1. 事务最 ...
- postgresql源码学习(49)—— MVCC⑤-cmin与cmax 同事务内的可见性判断
一. 难以理解的场景 postgresql源码学习(十九)-- MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客 在前篇的可见性判断中有 ...
- postgresql源码学习(53)—— vacuum②-lazy vacuum之heap_vacuum_rel函数
一. table_relation_vacuum函数 1. 函数定义 前篇最后(https://blog.csdn.net/Hehuyi_In/article/details/128749517),我 ...
- postgresql源码学习(52)—— vacuum①-准备工作与主要流程
关于vacuum的基础知识,参考,本篇从源码层继续学习 https://blog.csdn.net/Hehuyi_In/article/details/102992065 https://blog.c ...
- postgresql源码学习(57)—— pg中的四种动态库加载方法
一. 基础知识 1. 什么是库 库其实就是一些通用代码,可以在程序中重复使用,比如一些数学函数,可以不需要自己编写,直接调用相关函数即可实现,避免重复造轮子. 在linux中,支持两种类型的库: 1. ...
- postgresql源码学习(一)—— 源码编译安装与gdb调试入门
一. postgresql源码编译安装 因为只是用来调试的测试环境,把基本的软件装好和库建好就可以,一切从简. 1. 创建用户和目录 mkdir -p /data/postgres/base/ mkd ...
- postgresql源码学习(九)—— 常规锁②-强弱锁与Fast Path
一. 强锁与弱锁 根据兼容性表,彼此相容的3个锁(1-3级,AccessShareLock.RowShareLock.RowExclusiveLock)是弱锁,4级锁ShareUpdateExclus ...
最新文章
- 慕课网_《Java微信公众号开发进阶》学习总结
- python openpyxl读写xlsx_python高阶教程-python操作xlsx文件(openpyxl)
- java类中serialversionuid 作用 是什么?举个例子说明
- ARM系列处理器的分类
- 面向小姐姐的编程——JAVA面向对象之继承(三)
- Linux常用到的指令汇总
- Linux挂载iso文件步骤
- php 库下载,远程文件下载php类库
- 系统集成项目管理工程师10《项目干系人管理》
- 【Windows 11 SE精简版】——低配机的专属
- linux下的锐捷客户端
- 无线路由!RTS DTIM阈值、Beacon 周期如何设置多少可以加快路由
- 阿里云图片拼接指定尺寸
- EPIC的服务器稳定吗,epic国内有服务器吗(epic服务器在哪)
- SpringMVC对PathVariable的特殊字符.的处理默认是文件后缀
- 【SAP-CO】CO模块主要子模块相关概念
- 防火墙(360天堤)双因素身份认证解决方案
- Android运行项目时提示:No signature of method: build_*.android() is applicable for argument types
- 技巧| 如何打造高逼格耀斑效果
- Kappa Statistic