如Section 44.7.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 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 异常也会导致子事务回滚。
如果使用的 Python 版本早于 2.6,仍然可以通过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;
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。