从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)则不会导致回滚。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。