↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10
开发快照。 PostgreSQL 20devel 尚未正式发布,内容仍可能变化。

42.9. PL/Tcl 中的显式子事务 #

从第 42.8 节所述的数据库访问错误中恢复,可能会导致一种不理想的情况:某些操作在其中一个失败之前已经成功完成,而从该错误恢复之后,数据仍处于不一致状态。PL/Tcl 通过显式子事务为这个问题提供了解决方案。

考虑一个在两个账户之间执行转账的函数:

CREATE FUNCTION transfer_funds() RETURNS void AS $$
    if [catch {
        spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
        spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
    } errormsg] {
        set result [format "error transferring funds: %s" $errormsg]
    } else {
        set result "funds transferred successfully"
    }
    spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
$$ LANGUAGE pltcl;

如果第二个 UPDATE 语句引发异常,这个函数会记录这次失败,但第一个 UPDATE 的结果仍会被提交。换句话说,资金会从 Joe 的账户中扣除,却不会转入 Mary 的账户。这是因为每个 spi_exec 都是一个独立的子事务,而其中只有一个子事务发生了回滚。

要处理这类情况,可以把多个数据库操作包裹在一个显式子事务中,使其作为整体成功完成或整体回滚。PL/Tcl 提供了 subtransaction 命令来管理这一点。我们可以把函数改写为:

CREATE FUNCTION transfer_funds2() RETURNS void AS $$
    if [catch {
        subtransaction {
            spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
            spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
        }
    } errormsg] {
        set result [format "error transferring funds: %s" $errormsg]
    } else {
        set result "funds transferred successfully"
    }
    spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
$$ LANGUAGE pltcl;

注意,为达到这个目的,仍然需要使用 catch。否则错误会传播到函数顶层,从而阻止对 operations 表执行期望的插入。subtransaction 命令并不会捕获错误,它只保证在其作用域内执行的所有数据库操作,会在报告错误时一起回滚。

显式子事务会在其包含的 Tcl 代码报告任何错误时回滚,而不仅仅是在数据库访问导致错误时才回滚。因此,在 subtransaction 命令内部引发的普通 Tcl 异常也会导致子事务回滚。不过,从其中包含的 Tcl 代码中以非错误方式退出(例如由于 return)则不会导致回滚。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.