26

単純な質問であることを願っていますが、まともな答えをすぐに見つけられなかった質問です。PostgreSQL (具体的には、バージョン 9.0.4) のストアド プロシージャ (ユーザー定義 DB 関数) は、それ自体がトランザクションである SELECT ステートメントを介して呼び出されるため、本質的にトランザクション対応であると私は確信しています。では、ストアド プロシージャの分離レベルはどのように選択すればよいのでしょうか。他の DBMS では、目的のトランザクション ブロックは、目的の分離レベルがオプションのパラメーターである START TRANSACTION ブロックにラップされると思います。

具体的な例として、私がこれをやりたいとしましょう:

CREATE FUNCTION add_new_row(rowtext TEXT)
RETURNS VOID AS 
$$
BEGIN
        INSERT INTO data_table VALUES (rowtext);
        UPDATE row_counts_table SET count=count+1;
END;
$$  
LANGUAGE plpgsql
SECURITY DEFINER;

そして、この関数が常にシリアライズ可能なトランザクションとして実行されるようにしたいと想像してみてください (はい、はい、PostgreSQL SERIALIZABLE は適切にシリアライズ可能ではありませんが、それは重要ではありません)。として呼び出す必要はありません

START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT add_new_row('foo');
COMMIT;

では、必要な分離レベルを関数にプッシュするにはどうすればよいでしょうか? マニュアルBEGINにあるように、ステートメントに分離レベルを入れることはできないと思います

PL/pgSQL でステートメントをグループ化するための BEGIN/END の使用を、トランザクション制御のための同様の名前の SQL コマンドと混同しないことが重要です。PL/pgSQL の BEGIN/END はグループ化専用です。トランザクションを開始または終了しません。関数とトリガー プロシージャは常に、外部クエリによって確立されたトランザクション内で実行されます。実行するコンテキストがないため、そのトランザクションを開始またはコミットすることはできません。

私にとって最も明白なアプローチSET TRANSACTIONは、関数定義のどこかで使用することです。たとえば、次のようになります。

CREATE FUNCTION add_new_row(rowtext TEXT)
RETURNS VOID AS 
$$
BEGIN
        SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
        INSERT INTO data_table VALUES (rowtext);
        UPDATE row_counts_table SET count=count+1;
END;
$$  
LANGUAGE plpgsql
SECURITY DEFINER;

これは受け入れられますが、これが機能することに依存できるかどうかは明らかではありません。のドキュメントSET TRANSACTION言う

START TRANSACTION または BEGIN を事前に指定せずに SET TRANSACTION を実行すると、トランザクションがすぐに終了するため、効果がないように見えます。

単一のステートメントを呼び出すとSELECT add_new_row('foo');(自動コミットを無効にしていない場合)、SELECT がセッションのデフォルトの分離レベルで単一行のトランザクションとして実行されることが予想されるため、これは私を困惑させます。

マニュアルには次のようにも書かれています。

トランザクションの最初のクエリまたはデータ変更ステートメント (SELECT、INSERT、DELETE、UPDATE、FETCH、または COPY) が実行された後は、トランザクション分離レベルを変更できません。

したがって、関数がより低い分離レベルのトランザクション内から呼び出された場合はどうなるでしょうか。たとえば、次のようになります。

START TRANSACTION ISOLATION LEVEL READ COMMITTED;
UPDATE row_counts_table SET count=0;
SELECT add_new_row('foo');
COMMIT;

おまけの質問: 関数の言語によって違いはありますか? PL/pgSQL では、プレーン SQL とは異なる分離レベルを設定しますか?

私は標準と文書化されたベスト プラクティスのファンです。

4

4 に答える 4

20

そんなことはできません。

あなたができることは、現在のトランザクション分離レベルが何であるかを関数にチェックさせ、それがあなたが望むものでない場合は中止することです。SELECT current_setting('transaction_isolation')これは、実行して結果を確認することで実行できます。

于 2011-06-08T18:02:12.197 に答える
1

関数の言語はまったく違いはありません。

これは失敗します:

test=# create function test() returns int as $$
  set transaction isolation level serializable;
  select 1;
$$ language sql;
CREATE FUNCTION
test=# select test();
ERROR:  SET TRANSACTION ISOLATION LEVEL must be called before any query
CONTEXT:  SQL function "test" statement 1

特定の例では、最初のテーブルでトリガーを使用してこれを行うことができることに注意してください。デッドロックを回避するために、行数の更新が一貫した順序で行われていることを確認してください。繰り返し可能な読み取りモードで問題なく実行できます。

私は標準のファンです

PL/言語はプラットフォーム固有です。

于 2011-06-08T08:51:33.497 に答える
0

トランザクション分離とは、アクセスできる他の同時トランザクションで行われた変更を意味します。

実行をシリアル化する場合は、ロックを使用する必要があります。

行トリガーと更新カウントの後に使用できます。「UPDATErow_counts_table」はテーブルをロックし、すべてのトランザクションがシリアル化されます。遅いです。

あなたの例では、2つのステートメントがあります。挿入は実行されますが、更新は他のトランザクションを待機する必要があり、この期間中はカウントが無効です。

于 2011-06-08T14:10:08.320 に答える
0

PG では、手順は個別のトランザクションではありません。つまり、ストアド プロシージャは既存のトランザクションに参加します。

BEGIN TRAN

SELECT 1;
SELECT my_proc(99);

ROLLBACK TRAN;

そうは言っても、ストアド プロシージャの外にあるトランザクションが開始するトランザクション レベルを設定する必要があります。

1つのオプションは、主に使用したい分離で実行するようにサーバーを構成し、サーバー設定とは異なるエッジケースに対してSETを実行することです。

于 2011-06-08T18:03:07.907 に答える