On this page
43.9. PL/Tcl 中的显式子事务
如Section 43.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
的结果仍将提交。换句话说,这笔资金将从乔的帐户中提取,但不会转移到玛丽的帐户中。发生这种情况是因为每个spi_exec
是一个单独的子事务,并且这些子事务中只有一个被回滚。
要处理这种情况,您可以将多个数据库操作包装在一个显式子事务中,该事务将成功或整体回滚。 PL/Tcl 提供了一个subtransaction
命令来进行 Management。我们可以将函数重写为:
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
)不会导致回滚。