赞
踩
结束事务分为两类:
由于比较长,我们分开两篇记录。
会话1,隐式事务会自动提交
会话2
跟之前一样,我们先只是过一遍这个函数流程,里面具体调用函数的步骤这里都会先跳过,避免深陷泥潭。Lets Go!
调用栈如下
- /*
- * 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(); //恢复中断
- }
上面流程图里最重要的是事务日志写回磁盘部分: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章
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。