如第 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
)不会导致回滚。
如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用此表单报告文档问题。