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