从 第 44.6.2 节 中描述的数据库访问引起的错误中恢复,会导致一个不良情况:一些操作在其中一个操作失败之前成功执行,而在从该错误中恢复后,数据会处于不一致状态。PL/Python 通过显式子事务的形式为这个问题提供了解决方案。
假设一个函数实现两个账户之间的转账
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 异常也会导致子事务回滚。
如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符或需要进一步说明,请使用 此表格 报告文档问题。