無料スクリプト配布のPHP.TO   PHPの実用的なtips PHPマニュアル MySQLマニュアル Apacheマニュアル PostgreSQLマニュアル マニュアル検索    

43.8. 明示的サブトランザクション

項43.7.2 で説明したデータベースアクセスによって引き起こるエラーからの復旧は、操作の中の1つが失敗する前に、一部の操作が成功し、エラーからの復旧の後一貫性のないデータが残ってしまうという望ましくない状態を導く可能性があります。 PL/Pythonは明示的サブトランザクションにより、この問題の解法を提供します。

43.8.1. サブトランザクションのコンテキスト管理

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, 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, 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例外も同様にサブトランザクションをロールバックさせます。

43.8.2. Pythonの旧式バージョン

デフォルトでは、 with キーワードを使用したコンテキストマネージャ構文はPython 2.6で利用可能です。 これより古いバージョンのPythonでPL/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, 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;

注意: コンテキストマネージャはPython 2.5で実装されましたが、このバージョンで with 構文を使用するためには future文 を使用する必要があります。 しかし実装上の問題のためPL/Python関数ではfuture文を使用することができません。


powered by SEO.CUG.NET