46.7.2で説明したデータベースアクセスによって引き起こるエラーからの復旧は、操作の中の1つが失敗する前に、一部の操作が成功し、エラーからの復旧の後一貫性のないデータが残ってしまうという望ましくない状態を導く可能性があります。 PL/Pythonは明示的サブトランザクションにより、この問題の解法を提供します。
2つの口座の間の振替えを実装する関数を考えてみます。
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;
2番目のUPDATE
文が例外を発生させる結果となった場合、この関数はエラーを記録しますが、それにもかかわらず最初のUPDATE
はコミットされます。
言い換えると、資金はジョーの口座から引き落とされますが、メアリーの口座には移転しません。
こうした問題を防ぐために、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/catch
の使用がまだ必要なことに注意してください。
さもないと例外がPythonスタックの最上位まで伝播され、関数全体がPostgreSQLエラーにより中断され、この結果、operations
テーブルには挿入されるはずの行が存在しないことになります。
サブトランザクションのコンテキストマネージャはエラーを捕捉しません。
これはそのスコープの内側で実行されるデータベース操作すべてが、原子的にコミットされるかロールバックされるかだけを保証します。
サブトランザクションブロックのロールバックは、データベースアクセスを元にしたエラーによって引き起こる例外だけではなく、何らかの種類の例外終了でも起こります。
明示的なサブトランザクションブロックの内側で発生した通常の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;