当前位置:   article > 正文

postgresql源码学习(五)—— 提交事务_pgsql committransaction流程

pgsql committransaction流程

结束事务分为两类:

  • 提交:CommitTransaction
  • 回滚(包含清理):AbortTransaction

由于比较长,我们分开两篇记录。

一、 CommitTransaction

1. gdb测试

会话1,隐式事务会自动提交

会话2

       跟之前一样,我们先只是过一遍这个函数流程,里面具体调用函数的步骤这里都会先跳过,避免深陷泥潭。Lets Go!

2. 具体代码与跟踪

调用栈如下

  1. /*
  2. * CommitTransaction
  3. *
  4. * 注意:如果想修改此函数逻辑,最好同时也修改 PrepareTransaction 函数
  5. */
  6. static void
  7. CommitTransaction(void)
  8. {
  9. TransactionState s = CurrentTransactionState; //事务栈
  10. TransactionId latestXid;
  11. bool is_parallel_worker;
  12. is_parallel_worker = (s->blockState == TBLOCK_PARALLEL_INPROGRESS);

       我看到这有点疑惑,之前的文章不是说隐式事务不会有INPROGRESS的状态吗?于是又回去翻了一下,噢,原来是隐式事务没有TBLOCK_INPROGRESS状态(事务块状态而不是事务状态)。这俩蛮容易弄混的,需要注意。postgresql源码学习(二)—— 事务块状态转换_Hehuyi_In的博客-CSDN博客

  1. /* 如果是并行worker提交,强制进入并行模式. */
  2. if (is_parallel_worker)
  3. EnterParallelMode();
  4. ShowTransactionState("CommitTransaction");

  1. /*
  2. * 检查当前事务状态
  3. */
  4. if (s->state != TRANS_INPROGRESS)
  5. elog(WARNING, "CommitTransaction while in %s state",
  6. TransStateAsString(s->state));
  7. Assert(s->parent == NULL);
  8. /*
  9. * Do pre-commit processing that involves calling user-defined code, such
  10. * as triggers. SECURITY_RESTRICTED_OPERATION contexts must not queue an
  11. * action that would run here, because that would bypass the sandbox.
  12. * Since closing cursors could queue trigger actions, triggers could open
  13. * cursors, etc, we have to keep looping until there's nothing left to do.
  14. * 执行涉及调用用户定义代码(如触发器)的预提交处理。
  15. * 因为关闭游标可能会执行触发器,触发器可能打开游标等等,所以我们必须一直循环,直到没有什么可做的。
  16. */
  17. for (;;)
  18. {
  19. /*
  20. * 触发所有after触发器
  21. */
  22. AfterTriggerFireDeferred();
  23. /*
  24. * Close open portals (converting holdable ones into static portals).
  25. * If there weren't any, we are done ... otherwise loop back to check
  26. * if they queued deferred triggers. Lather, rinse, repeat.
  27. * 关闭打开的portals(将holdable portals转换为static portals),portals好像是一种内存资源?
  28. * 循环检查触发器队列直至全部关闭完
  29. */
  30. if (!PreCommit_Portals(false))
  31. break;
  32. }

  1. /*
  2. * The remaining actions cannot call any user-defined code, so it's safe
  3. * to start shutting down within-transaction services. But note that most
  4. * of this stuff could still throw an error, which would switch us into
  5. * the transaction-abort path.
  6. * 剩余的操作不能调用用户自定义的代码,因此可以安全地开始关闭事务内的服务。
  7. * 但是请注意,大多数这些动作仍然会抛出错误,这会导致流程切换到事务中止的路径上。
  8. */
  9. CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT
  10. : XACT_EVENT_PRE_COMMIT);

  1. /* 如果有并行workers, 需要清理 */
  2. if (IsInParallelMode())
  3. AtEOXact_Parallel(true);
  4. /* Shut down the deferred-trigger manager,关闭延迟触发器管理器 */
  5. AfterTriggerEndXact(true);
  6. /*
  7. * Let ON COMMIT management do its thing (must happen after closing
  8. * cursors, to avoid dangling-reference problems)
  9. * 由ON COMMIT管理器执行(必须在关闭游标后执行,避免挂起引用问题)
  10. */
  11. PreCommit_on_commit_actions();
  12. /*
  13. * Synchronize files that are created and not WAL-logged during this
  14. * transaction. This must happen before AtEOXact_RelationMap(), so that we
  15. * don't see committed-but-broken files after a crash.
  16. * 同步该事务创建的没有WAL日志记录的文件。这步必须在AtEOXact_RelationMap()前执行,避免在crash后我们看不到已提交但是broken了的文件
  17. */
  18. smgrDoPendingSyncs(true, is_parallel_worker);
  19. /* close large objects before lower-level cleanup ,在低级别清理前关闭大对象 */
  20. AtEOXact_LargeObject(true);
  21. /*
  22. * Insert notifications sent by NOTIFY commands into the queue. This
  23. * should be late in the pre-commit sequence to minimize time spent
  24. * holding the notify-insertion lock. However, this could result in
  25. * creating a snapshot, so we must do it before serializable cleanup.
  26. * 将NOTIFY命令发送的通知插入到队列中。这应该在预提交序列的末尾,以最小化持有notify-insertion锁的时间。然而这可能导致创建一个快照,因此必须在序列化清理前执行这步。
  27. */
  28. PreCommit_Notify();

  1. /*
  2. * Mark serializable transaction as complete for predicate locking
  3. * purposes. This should be done as late as we can put it and still allow
  4. * errors to be raised for failure patterns found at commit. This is not
  5. * appropriate in a parallel worker however, because we aren't committing
  6. * the leader's transaction and its serializable state will live on.
  7. */
  8. if (!is_parallel_worker)
  9. PreCommit_CheckForSerializationFailure();
  10. /* Prevent cancel/die interrupt while cleaning up,清理期间禁用中断,避免被打断 */
  11. HOLD_INTERRUPTS();
  12. /* Commit updates to the relation map --- do this as late as possible,提交更新到relation map -- 尽量晚地执行该动作 */
  13. AtEOXact_RelationMap(true, is_parallel_worker);

注意这里有特别重要的两步:

  • 设置事务状态为已提交 s->state = TRANS_COMMIT;  
  • 将事务日志写回磁盘 RecordTransactionCommit(),关于这步,以后还要继续深入学习
  1. /*
  2. * 设置事务状态
  3. */
  4. s->state = TRANS_COMMIT;
  5. s->parallelModeLevel = 0;
  6. if (!is_parallel_worker)
  7. {
  8. /*
  9. * We need to mark our XIDs as committed in pg_xact. This is where we
  10. * durably commit.将已提交的xid保存在pg_xact中(事务日志写回磁盘),这是我们持久化提交的位置
  11. */
  12. latestXid = RecordTransactionCommit();
  13. }

  1. else
  2. {
  3. /*
  4. * We must not mark our XID committed; the parallel leader is
  5. * responsible for that.并行worker则不需要标记,由并行leader处理
  6. */
  7. latestXid = InvalidTransactionId;
  8. /*
  9. * Make sure the leader will know about any WAL we wrote before it
  10. * commits.确保leader在提交之前知道worker写入的WAL
  11. */
  12. ParallelWorkerReportLastRecEnd(XactLastRecEnd);
  13. }
  14. TRACE_POSTGRESQL_TRANSACTION_COMMIT(MyProc->lxid);
  15. /*
  16. * Let others know about no transaction in progress by me. Note that this
  17. * must be done _before_ releasing locks we hold and _after_
  18. * RecordTransactionCommit.
  19. * 通知其他进程,本进程中已没有进行中的事务。
  20. * 注意,这必须在释放持有的锁之前、RecordTransactionCommit之后执行。
  21. */
  22. ProcArrayEndTransaction(MyProc, latestXid);
  23. /*
  24. * This is all post-commit cleanup. Note that if an error is raised here,
  25. * it's too late to abort the transaction. This should be just
  26. * noncritical resource releasing.
  27. * 这些都是提交后清理。
  28. * 请注意,如果这里才出现错误终止事务就太迟了,这应该是非关键的资源释放。
  29. * The ordering of operations is not entirely random. The idea is:
  30. * release resources visible to other backends (eg, files, buffer pins);
  31. * then release locks; then release backend-local resources. We want to
  32. * release locks at the point where any backend waiting for us will see
  33. * our transaction as being fully cleaned up.
  34. * 操作的顺序并不是完全随机的。
  35. * 其思想是:先释放对其他后台进程可见的资源(如文件、buffer pins),然后释放锁,最后释放后端本地资源。
  36. * 我们希望在所有等待本后台进程看到本事务被完全清理时才释放锁。
  37. * Resources that can be associated with individual queries are handled by
  38. * the ResourceOwner mechanism. The other calls here are for backend-wide
  39. * state.
  40. * 与单个查询关联的资源由ResourceOwner机制处理。
  41. * 这里的其他调用是针对后台进程范围状态的。
  42. */
  43. CallXactCallbacks(is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT
  44. : XACT_EVENT_COMMIT);
  45. ResourceOwnerRelease(TopTransactionResourceOwner,
  46. RESOURCE_RELEASE_BEFORE_LOCKS,
  47. true, true);

  1. /* Check we've released all buffer pins,检查所有已释放的buffer pins */
  2. AtEOXact_Buffers(true);
  3. /* Clean up the relation cache,清理关系缓存 */
  4. AtEOXact_RelationCache(true);
  5. /*
  6. * Make catalog changes visible to all backends. This has to happen after
  7. * relcache references are dropped (see comments for
  8. * AtEOXact_RelationCache), but before locks are released (if anyone is
  9. * waiting for lock on a relation we've modified, we want them to know
  10. * about the catalog change before they start using the relation).
  11. * 使目录更改对所有后台进程可见。
  12. * 这必须发生在relcache引用被删除之后(参见AtEOXact_RelationCache注释),
  13. * 但在锁被释放之前(如果有人在等待我们修改了的表的锁,我们希望他们在开始使用该表前知道目录的更改)。
  14. */
  15. AtEOXact_Inval(true);
  16. AtEOXact_MultiXact();
  17. ResourceOwnerRelease(TopTransactionResourceOwner,
  18. RESOURCE_RELEASE_LOCKS,
  19. true, true);
  20. ResourceOwnerRelease(TopTransactionResourceOwner,
  21. RESOURCE_RELEASE_AFTER_LOCKS,
  22. true, true);

  1. /*
  2. * Likewise, dropping of files deleted during the transaction is best done
  3. * after releasing relcache and buffer pins. (This is not strictly
  4. * necessary during commit, since such pins should have been released
  5. * already, but this ordering is definitely critical during abort.) Since
  6. * this may take many seconds, also delay until after releasing locks.
  7. * Other backends will observe the attendant catalog changes and not
  8. * attempt to access affected files.
  9. * 同样,在事务期间删除的文件的清理最好在释放relcache和buffer pin之后进行。(这在提交过程中并不是必须的,因为这样的pins应该已经被释放了,但是该顺序在中止过程中绝对是至关重要的。)
  10. * 因为这可能需要较长的时间,所以也要延迟到释放锁之后。
  11. * 其他后台进程将监控相关的catalog更改,不尝试访问受影响的文件。
  12. */
  13. smgrDoPendingDeletes(true);
  14. AtCommit_Notify();
  15. //一大波资源清理
  16. AtEOXact_GUC(true, 1);
  17. AtEOXact_SPI(true);
  18. AtEOXact_Enum();
  19. AtEOXact_on_commit_actions(true);
  20. AtEOXact_Namespace(true, is_parallel_worker);
  21. AtEOXact_SMgr();
  22. AtEOXact_Files(true);
  23. AtEOXact_ComboCid();
  24. AtEOXact_HashTables(true);
  25. AtEOXact_PgStat(true, is_parallel_worker);
  26. AtEOXact_Snapshot(true, false);
  27. AtEOXact_ApplyLauncher(true);
  28. pgstat_report_xact_timestamp(0);
  29. CurrentResourceOwner = NULL;
  30. ResourceOwnerDelete(TopTransactionResourceOwner);
  31. s->curTransactionOwner = NULL;
  32. CurTransactionResourceOwner = NULL;
  33. TopTransactionResourceOwner = NULL;
  34. AtCommit_Memory();

  1. // 重置事务栈变量
  2. s->fullTransactionId = InvalidFullTransactionId;
  3. s->subTransactionId = InvalidSubTransactionId;
  4. s->nestingLevel = 0;
  5. s->gucNestLevel = 0;
  6. s->childXids = NULL;
  7. s->nChildXids = 0;
  8. s->maxChildXids = 0;
  9. XactTopFullTransactionId = InvalidFullTransactionId;
  10. nParallelCurrentXids = 0;

  1. /*
  2. * done with commit processing, set current transaction state back to default,完成事务提交后,将当前事务状态改回TRANS_DEFAULT
  3. */
  4. s->state = TRANS_DEFAULT;
  5. RESUME_INTERRUPTS(); //恢复中断
  6. }

3. 主要流程图

二、 事务日志写回磁盘

       上面流程图里最重要的是事务日志写回磁盘部分:RecordTransactionCommit(),保证已提交数据不会丢失。

  1. if ((wrote_xlog && markXidCommitted &&
  2. synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
  3. forceSyncCommit || nrels > 0) //判断是否要求同步提交,nrels > 0暂时没懂
  4. {
  5. XLogFlush(XactLastRecEnd); //如果是,则必须先写日志
  6. /*
  7. * Now we may update the CLOG, if we wrote a COMMIT record above。更新CLOG,更新事务状态
  8. */
  9. if (markXidCommitted)
  10. TransactionIdCommitTree(xid, nchildren, children);
  11. }
  12. else //如果是异步提交,不要求先写WAL,但崩溃时可能有数据丢失
  13. {
  14. /*设置异步提交最新的LSN */
  15. XLogSetAsyncXactLSN(XactLastRecEnd);
  16. /*
  17. * 将最新的LSN保存到异步提交的事务组中,不要求先写WAL即可提交事务,同时将事务状态保存到CLOG中
  18. */
  19. if (markXidCommitted)
  20. TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);
  21. }

RecordTransactionCommit函数内的主要调用流程如下,本篇我们暂时不详细分析了

参考:

PostgreSQL技术内幕:事务处理深度探索》第1章

PostgreSQL数据库内核分析》第7章

PostgreSQL 源码解读(122)- MVCC#7(提交事务-整体流程)_ITPUB博客

PostgreSQL 源码解读(123)- MVCC#8(提交事务-实际提交过程)_ITPUB博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/135170
推荐阅读
相关标签
  

闽ICP备14008679号