46.8. 显式子 Transaction

Section 46.7.2中所述从数据库访问引起的错误中恢复可能会导致不希望的情况,其中某些操作在其中一个失败之前就已经成功,并且从该错误中恢复后,数据处于不一致状态。 PL/Python 以显式子事务的形式提供了针对此问题的解决方案。

46 .8.1. 子事务上下文 Management 器

考虑一个实现两个帐户之间转移的函数:

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, 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的结果仍将提交。换句话说,这笔资金将从乔的帐户中提取,但不会转移到玛丽的帐户中。

为避免此类问题,您可以将plpy.execute调用包装在显式子事务中。 plpy模块提供了一个辅助对象,以 Management 通过plpy.subtransaction()函数创建的显式子事务。通过此函数创建的对象实现上下文 Management 器界面。使用显式子事务,我们可以将函数重写为:

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, 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/catch。否则,异常将传播到 Python 堆栈的顶部,并导致整个函数因 PostgreSQL 错误而中止,因此operations表将不会插入任何行。子事务上下文 Management 器不会捕获错误,它只是确保在其作用域内执行的所有数据库操作都将被自动提交或回退。子事务块的回滚发生在任何类型的异常 Export 上,不仅是由数据库访问引起的错误引起的。在显式子事务块内引发的常规 Python 异常也会导致子事务回滚。

46 .8.2. 较旧的 Python 版本

Python 2.6 默认情况下使用with关键字的上下文 Management 器语法。如果将 PL/Python 与较旧的 Python 版本一起使用,则仍然可以使用显式子事务,尽管不是透明的。您可以使用enterexit便捷别名来调用子事务 Management 器的__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, 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;

Note

尽管上下文 Management 器是在 Python 2.5 中实现的,但要在该版本中使用with语法,您需要使用future statement。但是,由于实现细节,您不能在 PL/Python 函数中使用将来的语句。