↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1

46.8. 显式子事务 #

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

46.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 plpythonu;

如果第二条 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 plpythonu;

注意,仍需要使用 try/except。否则,异常会传播到 Python 调用栈顶层,使整个函数因 PostgreSQL 错误而中止,从而不会向 operations 表插入任何行。子事务上下文管理器不会捕获错误,只保证在其作用域内执行的所有数据库操作以原子方式提交或回滚。任何异常退出都会使子事务块回滚,并不限于数据库访问错误。显式子事务块中抛出的普通 Python 异常,也会导致该子事务回滚。

46.8.2. 较早的 Python 版本 #

使用 with 关键字的上下文管理器语法,从 Python 2.6 起默认可用。为了兼容更早的 Python 版本,可以通过便捷别名 enter 和 exit,调用子事务管理器的 __enter__ 和 __exit__ 函数。转账示例函数可以写成:

CREATE FUNCTION transfer_funds_old() RETURNS void AS $$
try:
    subxact = plpy.subtransaction()
    subxact.enter()
    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:
        import sys
        subxact.exit(*sys.exc_info())
        raise
    else:
        subxact.exit(None, None, None)
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 plpythonu;

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。英文原文本身的问题请通过上游文档表单反馈给 PostgreSQL 文档维护者。