受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

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

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

提交更正

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