目 录CONTENT

文章目录

【GaussDB】内核事务异步提交(延迟清理)相关问题记录

DarkAthena
2026-09-15 / 0 评论 / 0 点赞 / 0 阅读 / 0 字

【GaussDB】内核事务异步提交(延迟清理)相关问题记录

背景

【GaussDB】会话里出现大量idle in transaction状态的问题排查

【GaussDB】排查一起ustore表在存储过程里膨胀的问题

谁能联想到,上面这两个问题竟然存在相关性。

近期发现有好几个问题似乎都与GaussDB的异步事务提交功能相关,(注意这里的异步提交并非指的传统意义上的fsync=offsynchronous_commit=off,而是对于"只读事务",在事务结束时,将资源清理等动作做异步处理,不阻塞客户端,或者叫做延迟清理。)(该特性在GaussDB506版本引入)

但目前(20260720),华为研发并未完全确认这些问题是否都与异步事务提交功能相关。这里我仅以个人视角来进行一下分析。

GaussDB内核版本号为506.0.0SPC0500

问题现象

  1. pg_stat_activity.state 在pbe自动提交情况下,如果不显式执行commit,任意SQL执行后,会显示为idle in transaction状态,但xact_start为空。(稳定复现)https://www.darkathena.top/archives/gaussdb-idle-in-transaction-bug-analyze
  2. pg_stat_all_tables里的增删改计数器在PBE自动提交时,存储过程里带dml+commit,执行存储过程,如果不在外面再执行一次commit,计数器不变。(稳定复现,我没单独写文章)
  3. ustore的表,在pbe自动提交情况下,存储过程里delete+insert+commit,循环执行这个存储过程,操作大量数据且磁盘性能较差时,大概率insert完全无法复用delete释放的空间,造成表随执行次数正比例膨胀。(概率性复现)https://www.darkathena.top/archives/gaussdb-an-ustore-table-bloat-in-plsql

其实可能还有一些其他问题,但复现条件比较严苛,我暂时不好复现,因此仅作为怀疑点。

分析

由于没有GaussDB的源码,这里参考一下openGauss(虽然openGauss上没有复现相关问题),对应的pr :
https://gitcode.com/opengauss/openGauss-server/pull/8094

修改前:

    if (u_sess->xact_cxt.pbe_execute_complete == true) {
        finish_xact_command();
    } else {
        u_sess->xact_cxt.pbe_execute_complete = true;
    }

修改后:

    if (u_sess->xact_cxt.pbe_execute_complete == true) {
        if (u_sess->attr.attr_common.enable_beta_features && GetTopTransactionIdIfAny() == InvalidTransactionId) {
            u_sess->xact_cxt.commit_pending = true;
        } else {
            finish_xact_command();
            u_sess->xact_cxt.commit_pending = false;
        }
    } else {
        u_sess->xact_cxt.pbe_execute_complete = true;
    }

这段代码是 S报文(exec_sync_message)里处理的 ,Sync(同步)报文是前端(客户端)在扩展查询协议中,用于结束一个消息序列并建立错误恢复点的核心控制消息。

也就是原本pbe下都会执行到 finish_xact_command(),但优化后,GetTopTransactionIdIfAny() == InvalidTransactionId这个场景就不执行finish_xact_command();了。

GetTopTransactionIdIfAny是当前会话里顶层事务的xid ,而InvalidTransactionId 是0。

什么时候GetTopTransactionIdIfAny会是0呢?一般是所谓的只读查询时会是0。但是,在GaussDB里,如果是存储过程里有提交,且提交后没有新的dml,这个事务结束后的顶层xid也会变成0。(做事务的和做PLSQL的研发没有统一原则?)

在pg_stat_activity.top_xid (GaussDB有这个字段,openGauss没有) 中可以实时看到顶层xid的情况。通过加上适当的pg_sleep,在存储过程执行期间,可以看到执行dml时,top_xid>0,但当存储过程执行到commit后,top_xid就变成0了,也就是最终这个存储过程执行完后top_xid变为0,但是实际上有数据修改,也就是说,这个事务并不是一个只读事务,所以GetTopTransactionIdIfAny() == InvalidTransactionId这个判断条件有问题!

所以,既然已知了问题发生点,那么这个问题到底会影响哪些东西,关键就是看没被执行的finish_xact_command()里到底处理些什么东西了。

由于同样还是没有GaussDB源码,依旧参考openGauss(再次说明,openGauss上我并未测出类似问题),

User: 查一下这个函数#sym:finish_xact_command 会做些什么事情

GitHub Copilot:

finish_xact_command 是 openGauss 中的一个便捷函数,用于完成当前事务命令的提交过程。它与 start_xact_command 配对使用,管理事务的生命周期。

主要功能:

  1. 检查事务状态:仅在 xact_started 为 true 时执行操作

  2. 取消语句超时:调用 disable_sig_alarm(true) 取消任何活动的语句超时计时器

  3. 提交事务:调用 CommitTransactionCommand() 提交当前事务

  4. 内存检查(调试模式):

  • 如果启用了 MEMORY_CONTEXT_CHECKING,检查所有未在提交过程中释放的内存上下文

  • 对于线程池工作线程,还会检查会话内存上下文

  1. 内存统计(调试模式):如果启用了 SHOW_MEMORY_STATS,打印内存统计信息用于泄漏追踪

  2. 重置状态:将 t_thrd.postgres_cxt.xact_started 设置为 false

典型调用场景:

  • 事务控制语句(COMMIT、ROLLBACK)执行后
  • 查询执行完成后
  • 在批处理操作之间
  • 在处理完客户端请求后

这个函数是 PostgreSQL 事务管理的一部分,确保事务正确提交并释放相关资源。

User: CommitTransactionCommand() 里具体做了些什么呢?

GitHub Copilot:

现在让我再查看 CommitTransaction() 的实现,它是最核心的提交逻辑:

CommitTransactionCommand() 是 openGauss 事务管理的核心函数之一,位于 xact.cpp 第 4234 行。它根据当前事务块状态 (TBlockState) 采取不同行为,本质上是一个状态机。

核心逻辑(switch 状态机)

状态处理
TBLOCK_STARTED(不在事务块中)调用 CommitTransaction() 提交,回到 DEFAULT 空闲状态
TBLOCK_BEGIN(刚执行完 BEGIN)将状态转为 TBLOCK_INPROGRESS(不提交,仅推进状态)
TBLOCK_INPROGRESS(事务块进行中)CommandCounterIncrement()(命令计数器 +1,使当前命令的修改对后续命令可见,不提交
TBLOCK_END(执行完 COMMIT)调用 CommitTransaction(),回到 DEFAULT
TBLOCK_ABORT / TBLOCK_SUBABORT(已中止)什么都不做,等待 ROLLBACK
TBLOCK_ABORT_END(执行完 ROLLBACK)CleanupTransaction() 清理后回到 DEFAULT
TBLOCK_ABORT_PENDING(需要中止)AbortTransaction()CleanupTransaction()
TBLOCK_PREPARE(两阶段提交)PrepareTransaction()
TBLOCK_SUBBEGIN / TBLOCK_SUBRELEASE / TBLOCK_SUBCOMMIT管理子事务(savepoint)的提交或回滚
TBLOCK_SUBABORT_PENDING / TBLOCK_SUBABORT_END先中止再清理子事务,递归调用自身

最关键的 TBLOCK_STARTED → CommitTransaction()

当不在显式事务块中(TBLOCK_STARTED),会调用真正的提交函数 CommitTransaction(),它做以下事情:

  1. 触发器处理:循环执行 AfterTriggerFireDeferred(),直到所有延迟触发器完成
  2. 资源清理ResourceOwnerRelease() 释放资源
  3. 存储管理器提交smgrDoPendingSyncs() 同步文件
  4. 缓冲区刷写FlushBufferPool() / BufmgrCommit()
  5. 记录提交RecordTransactionCommit() 写 XLOG commit 记录
  6. 异步通知AtCommit_Notify() 发送 LISTEN/NOTIFY
  7. 缓存失效AtEOXact_Inval() 处理共享缓存失效消息
  8. 释放锁LockReleaseAll() 释放事务持有的锁
  9. 统计更新pgstat_xact_commit() 报告统计信息
  10. 清理内存上下文、重置全局状态

总结

CommitTransactionCommand() 的本质是一个调度器

  • 隐式事务(autocommit)中:直接完成真正的提交
  • 显式事务块中:仅做命令计数器递增,不提交
  • 事务结束(COMMIT)时:完成真正的提交
  • 事务中止(ROLLBACK)时:执行清理

在你选中的第 5925 行上下文中,它在 exec_simple_query 的最后阶段被调用,用于确保该查询的事务命令被正确完成。

这个问题似乎比预想的要严重,在pbe的autocommit情况下,没有"真正提交"(主要是状态更新和资源清理,暂不讨论数据可见性的问题,因为目前暂未发现数据可见性方面有上报问题),这篇文章我都不敢继续写下去了......不过这毕竟是看的openGauss源码,GaussDB里这一块会需要处理哪些东西,有没有减少或者新增,甚至执行流程可能全变了,都是不确定的。

写到这里,暂不发布本文,等一手华为的消息。


20260825更新

华为有了回复,他们内部拉了很多板块的内核研发人员一起进行了讨论分析研究,最终得出结论:

ustore表膨胀是因为统计信息上报的过程没有执行(此处统计信息不是指pg_statistic/pg_class,而是指pg_stat_all_tables),这个问题也会导致pg_stat_all_tables没被更新,这就是撞上了之前异步事务提交(延迟清理功能)发现的这个BUG。

具体就是因为这种场景下,统计信息没有上报,导致依赖于统计信息上报过程里执行的动作都没做,而新版本针对统计信息没有上报的问题做了修复,所以依赖它的问题都解决了。

但是华为研发认为异步事务提交(已改口叫延迟清理功能)的这个BUG目前只会导致这三个已知问题。然后再我再三追问下,华为研发说autovacuum和autoanalyze也会受影响,可能会影响ubtree索引大小以及表统计信息不准,进而导致SQL性能或执行计划受影响。

但是我仍然有疑问,新版本的修复逻辑还是会导致含dml+commit的存储过程走到异步事务提交(延迟清理)的逻辑,只是避免了异步任务的遗漏(是否会在极小的延迟处理期间内,其他线程获取到的不是最新的状态或统计信息?)。


异步事务提交(延迟清理)的这个BUG,从首次发现idle_in_transaction状态不对的问题开始,已经持续跟踪了3个多月,我没有GaussDB的源码,用openGauss的源码无法形成对GaussB的绝对质疑。不过和华为的沟通过程中,我发现GaussDB里上报统计信息这段逻辑和openGauss有所区别,所以华为给的最终答复应该是可信的。

我当前这个驻场项目合同也快到期了,后续无法在此客户处继续监控该问题是否存在其他影响。深思熟虑后,我还是打算把这篇文章发出来,供大家参考。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论