↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1
开发快照。 PostgreSQL 20devel 尚未正式发布,内容仍可能变化。

44.8. 显式子事务 #

如第 44.7.2 节中所述,从数据库访问引发的错误中恢复,可能会造成一种不理想的情况:在某个操作失败之前,其他一些操作已经成功,而在从该错误恢复后,数据却处于不一致状态。PL/Python 以显式子事务的形式为这个问题提供了解决方案。

44.8.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 异常也会导致子事务回滚。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.