结束事务分为两类:

  • 提交: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源码学习(五)—— 提交事务相关推荐

  1. PostgreSQL源码学习(1)--PG13代码结构

    PostgreSQL源码学习(1)–PG13代码结构 PostgreSQL代码结构 Bootstrap:用于支持Bootstrap运行模式,该模式主要用来创建初始的模板数据库. Main:主程序模块, ...

  2. PostgreSQL源码学习(一)编译安装与GDB入门

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 PostgreSQL源码学习(一)编译安装与GDB入门 前言 一.安装PostgreSQL 1.获取源码 2.配置 3.编译 3.安装 ...

  3. postgresql源码学习(27)—— 事务日志⑦-日志落盘上层函数 XLogFlush

    一. 预备知识 1. XLOG什么时候需要落盘 事务commit之前 log buffer被覆盖之前 后台进程定期落盘 2. 两个核心结构体 这两个结构体定义代码在xlog.c,它们在日志落盘过程中非 ...

  4. postgresql源码学习(51)—— 提交日志CLOG 原理 用途 管理函数

    一. CLOG是什么 CLOG(commit log)记录事务的最终状态. 物理上,是$PGDATA/pg_xact目录下的一些文件 逻辑上,是一个数组,下标为事务id,值为事务最终状态 1. 事务最 ...

  5. postgresql源码学习(49)—— MVCC⑤-cmin与cmax 同事务内的可见性判断

    一. 难以理解的场景 postgresql源码学习(十九)-- MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客 在前篇的可见性判断中有 ...

  6. postgresql源码学习(53)—— vacuum②-lazy vacuum之heap_vacuum_rel函数

    一. table_relation_vacuum函数 1. 函数定义 前篇最后(https://blog.csdn.net/Hehuyi_In/article/details/128749517),我 ...

  7. postgresql源码学习(52)—— vacuum①-准备工作与主要流程

    关于vacuum的基础知识,参考,本篇从源码层继续学习 https://blog.csdn.net/Hehuyi_In/article/details/102992065 https://blog.c ...

  8. postgresql源码学习(57)—— pg中的四种动态库加载方法

    一. 基础知识 1. 什么是库 库其实就是一些通用代码,可以在程序中重复使用,比如一些数学函数,可以不需要自己编写,直接调用相关函数即可实现,避免重复造轮子. 在linux中,支持两种类型的库: 1. ...

  9. postgresql源码学习(一)—— 源码编译安装与gdb调试入门

    一. postgresql源码编译安装 因为只是用来调试的测试环境,把基本的软件装好和库建好就可以,一切从简. 1. 创建用户和目录 mkdir -p /data/postgres/base/ mkd ...

  10. postgresql源码学习(九)—— 常规锁②-强弱锁与Fast Path

    一. 强锁与弱锁 根据兼容性表,彼此相容的3个锁(1-3级,AccessShareLock.RowShareLock.RowExclusiveLock)是弱锁,4级锁ShareUpdateExclus ...

最新文章

  1. 慕课网_《Java微信公众号开发进阶》学习总结
  2. python openpyxl读写xlsx_python高阶教程-python操作xlsx文件(openpyxl)
  3. java类中serialversionuid 作用 是什么?举个例子说明
  4. ARM系列处理器的分类
  5. 面向小姐姐的编程——JAVA面向对象之继承(三)
  6. Linux常用到的指令汇总
  7. Linux挂载iso文件步骤
  8. php 库下载,远程文件下载php类库
  9. 系统集成项目管理工程师10《项目干系人管理》
  10. 【Windows 11 SE精简版】——低配机的专属
  11. linux下的锐捷客户端
  12. 无线路由!RTS DTIM阈值、Beacon 周期如何设置多少可以加快路由
  13. 阿里云图片拼接指定尺寸
  14. EPIC的服务器稳定吗,epic国内有服务器吗(epic服务器在哪)
  15. SpringMVC对PathVariable的特殊字符.的处理默认是文件后缀
  16. 【SAP-CO】CO模块主要子模块相关概念
  17. 防火墙(360天堤)双因素身份认证解决方案
  18. Android运行项目时提示:No signature of method: build_*.android() is applicable for argument types
  19. 技巧| 如何打造高逼格耀斑效果
  20. Kappa Statistic

热门文章

  1. eclipse使用SVN进行同步时,发生错误的解决办法
  2. iOS学习一些资料的整理
  3. js 判断页面 第一次加载or刷新
  4. Matlab 5G模块学习
  5. 人生需要放下的八样东西
  6. 读书笔记 - 朱赟的技术管理课
  7. C++命名规则记录(参考谷歌编码规范)
  8. 爬取个人随笔内容——练手,待补充
  9. 完全搞定pagefile.pif(ExERoute木马)病毒
  10. STM32串口助手小问题