2024 年 9 月 26 日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不受支持的版本:11 / 10

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)不会导致回滚。

提交更正

如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符或需要进一步说明的内容,请使用 此表格 报告文档问题。