PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

44.7. 显式子事务 #

44.7.1. 子事务上下文管理器

如 Section 44.6.2 所述从数据库访问导致的错误 中恢复,可能导致一种不良情况:某个操作失败之前,一些操作已经 成功,而错误恢复之后数据会处于不一致的状态。PL/Python 以显式 子事务的形式为这个问题提供了解决方案。

44.7.1. 子事务上下文管理器 #

考虑一个实现两个账户之间转账的函数:

CREATE FUNCTION transfer_funds() RETURNS void AS $$
try:
    plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
    plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
    result = "error transferring funds: %s" % e.args
else:
    result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

如果第二条 UPDATE 语句导致异常被抛出,该函数 会报告错误,但第一条 UPDATE 的结果仍然会被 提交。换句话说,资金会从 Joe 的账户中划出,但不会被转入 Mary 的账户。

要避免这类问题,可以把 plpy.execute 调用包装在 一个显式子事务中。plpy 模块提供了一个帮助 对象来管理显式子事务,它通过 plpy.subtransaction() 函数创建。 该函数创建的对象实现了 上下文管理器接口。使用显式子事务,我们可以把函数重写为:

CREATE FUNCTION transfer_funds2() RETURNS void AS $$
try:
    with plpy.subtransaction():
        plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
        plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
    result = "error transferring funds: %s" % e.args
else:
    result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;

注意,仍然需要使用 try/except。 否则异常会传播到 Python 栈的顶端,导致整个函数以 PostgreSQL 错误中止,于是 operations 表中不会插入任何行。 子事务上下文管理器不捕获错误,它只确保其作用域内执行的所有数据库 操作要么原子地提交、要么原子地回滚。 子事务块在任何类型的异常退出时都会回滚,不仅限于由数据库访问错误 导致的退出。在显式子事务块内抛出的普通 Python 异常同样会导致 子事务回滚。