簡単な質問だといいのですが、まともな答えがすぐに見つからない質問です。 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;
_
これは受け入れられますが、これが機能することを信頼できるかどうかは明らかではありません。 documentation for _SET TRANSACTION
_は言う
事前のSTARTTRANSACTIONまたはBEGINなしでSETTRANSACTIONが実行された場合、トランザクションはすぐに終了するため、効果がないように見えます。
単独の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とは異なる方法で分離レベルを設定しますか?
私は標準と文書化されたベストプラクティスのファンなので、まともな参照があれば幸いです。
あなたはそれをすることはできません。
あなたができることは、関数に現在のトランザクション分離レベルが何であるかをチェックさせ、それがあなたが望むものでない場合は中止することです。これを行うには、SELECT current_setting('transaction_isolation')
を実行してから結果を確認します。
関数の言語は何の違いもありません。
これは失敗します:
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 /言語はプラットフォーム固有です。
トランザクション分離とは、アクセスできる他の同時トランザクションで行われた変更を意味します。
実行をシリアル化する場合は、ロックを使用する必要があります。
行トリガーと更新カウントの後に使用できます。 「UPDATErow_counts_table」はテーブルをロックし、すべてのトランザクションがシリアル化されます。 遅いです。
あなたの例では、2つのステートメントがあります。挿入は実行されますが、更新は他のトランザクションを待機する必要があり、この期間中はカウントが無効です。
PGでは、手順は個別のトランザクションではありません。つまり、ストアドプロシージャは既存のトランザクションに参加します。
BEGIN TRAN
SELECT 1;
SELECT my_proc(99);
ROLLBACK TRAN;
そうは言っても、ストアドプロシージャの外部にあるトランザクションが開始されるトランザクションレベルを設定する必要があります。
1つのオプションは、主に使用する分離で実行するようにサーバーを構成し、サーバーの設定と異なるEdgeの場合にSETを実行することです。