バージョンごとのドキュメント一覧

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

<title>Explicit Subtransactions</title>

Recovering from errors caused by database access as described in <xref linkend="plpython-trapping"/> can lead to an undesirable situation where some operations succeed before one of them fails, and after recovering from that error the data is left in an inconsistent state. PL/Python offers a solution to this problem in the form of explicit subtransactions. 44.6.2で説明したデータベースアクセスによって引き起こるエラーからの復旧は、操作の中の1つが失敗する前に、一部の操作が成功し、エラーからの復旧の後一貫性のないデータが残ってしまうという望ましくない状態を導く可能性があります。 PL/Pythonは明示的サブトランザクションにより、この問題の解法を提供します。

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

<title>Subtransaction Context Managers</title>

Consider a function that implements a transfer between two accounts: 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 plpython3u;

If the second <literal>UPDATE</literal> statement results in an exception being raised, this function will report the error, but the result of the first <literal>UPDATE</literal> will nevertheless be committed. In other words, the funds will be withdrawn from Joe's account, but will not be transferred to Mary's account. 2番目のUPDATE文が例外を発生させる結果となった場合、この関数はエラーを記録しますが、それにもかかわらず最初のUPDATEはコミットされます。 言い換えると、資金はジョーの口座から引き落とされますが、メアリーの口座には移転しません。

To avoid such issues, you can wrap your <literal>plpy.execute</literal> calls in an explicit subtransaction. The <literal>plpy</literal> module provides a helper object to manage explicit subtransactions that gets created with the <literal>plpy.subtransaction()</literal> function. Objects created by this function implement the <ulink url="https://docs.python.org/library/stdtypes.html#context-manager-types"> context manager interface</ulink>. Using explicit subtransactions we can rewrite our function as: こうした問題を防ぐために、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;

Note that the use of <literal>try</literal>/<literal>except</literal> is still required. Otherwise the exception would propagate to the top of the Python stack and would cause the whole function to abort with a <productname>PostgreSQL</productname> error, so that the <literal>operations</literal> table would not have any row inserted into it. The subtransaction context manager does not trap errors, it only assures that all database operations executed inside its scope will be atomically committed or rolled back. A rollback of the subtransaction block occurs on any kind of exception exit, not only ones caused by errors originating from database access. A regular Python exception raised inside an explicit subtransaction block would also cause the subtransaction to be rolled back. try/exceptの使用がまだ必要なことに注意してください。 さもないと例外がPythonスタックの最上位まで伝播され、関数全体がPostgreSQLエラーにより中断され、この結果、operationsテーブルには挿入されるはずの行が存在しないことになります。 サブトランザクションのコンテキストマネージャはエラーを捕捉しません。 これはそのスコープの内側で実行されるデータベース操作すべてが、原子的にコミットされるかロールバックされるかだけを保証します。 サブトランザクションブロックのロールバックは、データベースアクセスを元にしたエラーによって引き起こる例外だけではなく、何らかの種類の例外終了でも起こります。 明示的なサブトランザクションブロックの内側で発生した通常のPython例外も同様にサブトランザクションをロールバックさせます。