17

他の質問で提案されているトランザクション分離に関するpostgresのドキュメントを徹底的に読んでいますが、「述語のロック」についてはまだ理解できていません。

誰かが私を啓発できることを願っています:-)

ドキュメントによると: PostgreSQL の述語ロックは、他のほとんどのデータベース システムと同様に、トランザクションによって実際にアクセスされるデータに基づいています。

それは良さそうですが、なぜ次のようなことが起こっているのでしょうか?

CREATE TABLE mycustomer(cid integer PRIMARY KEY, licenses integer);
CREATE TABLE mydevice(id integer PRIMARY KEY, cid integer REFERENCES 
mycustomer (cid), status varchar(10));

INSERT INTO mycustomer(cid, licenses) VALUES (1, 5);
INSERT INTO mycustomer(cid, licenses) VALUES (2, 5);

    Request 1                            Request2
BEGIN TRANSACTION ISOLATION 
LEVEL SERIALIZABLE;
                                         BEGIN TRANSACTION ISOLATION 
                                         LEVEL SERIALIZABLE;
SELECT * from mydevice where cid = 1;

                                         SELECT * from mydevice where cid = 2;
INSERT INTO mydevice(id, cid, status) 
VALUES (1, 1, 'ok');

                                         INSERT INTO mydevice(id, cid, status)         
                                         VALUES (2, 2, 'ok');
commit;
(=ok)                                 
                                         commit;
                                         (=rollback)

リクエスト 1 とリクエスト 2 からの挿入は以前の読み取りと競合しないため、エラーが発生することはないことを理解しています。「エラー: トランザクション間の読み取り/書き込みの依存関係のため、アクセスをシリアル化できませんでした」というメッセージが表示されるのはなぜですか。

ご想像のとおり、すべての同時リクエストはその詳細に関係なくロールバックされるため、前述の動作が発生することはありません。私のビジネス シナリオでは、同じ単一の顧客のデータを (例のデバイスに従って) 挿入しているときにのみ、同時要求をロールバックしたいと考えています。

これらの操作は Java アプリケーションから実行されます。原則として、ニーズを満たすためにロック テーブルを作成することを考えています。何か案は?

どうもありがとう!

4

2 に答える 2

20

トランザクション分離ページから:

クエリの実行中に取得される特定のロックは、クエリで使用されるプランによって異なります。また、複数のより細かいロック(タプルロックなど)を組み合わせて、より粗いロック(ページロックなど)を作成することもできます。ロックの追跡に使用されるメモリの枯渇を防ぐためのトランザクション。

..。

  • シーケンシャルスキャンでは、常にリレーションレベルの述語ロックが必要になります。これにより、シリアル化の失敗率が高くなる可能性があります。

そのEXPLAIN上でSELECT、クエリプランがどのように実行されているかを知ることができますが、テーブルが小さい(または空である!)場合、PostgreSQLは、インデックスを参照する代わりに、ほぼ確実にシーケンシャルスキャンを選択します。これにより、テーブル全体で述語ロックが発生し、別のトランザクションがテーブルに対して何かを実行するたびにシリアル化が失敗します。

私のシステムでは:

isolation=# EXPLAIN SELECT * from mydevice where cid = 1;
                        QUERY PLAN                        
----------------------------------------------------------
 Seq Scan on mydevice  (cost=0.00..23.38 rows=5 width=46)
   Filter: (cid = 1)
(2 rows)

インデックスを追加して、それを強制的に使用することができます。

isolation=# CREATE INDEX mydevice_cid_key ON mydevice (cid);
CREATE INDEX
isolation=# SET enable_seqscan = off;
SET
isolation=# EXPLAIN SELECT * from mydevice where cid = 1;
                                    QUERY PLAN                                    
----------------------------------------------------------------------------------
 Index Scan using mydevice_cid_key on mydevice  (cost=0.00..8.27 rows=1 width=46)
   Index Cond: (cid = 1)
(2 rows)

ただし、これは正しい解決策ではありません。少しバックアップしましょう。

シリアル化可能とは、実際にこれらのトランザクションを同時に実行しているにもかかわらず、トランザクションが次々に実行された場合とまったく同じ効果を持つことを保証することを意味します。PostgreSQLには無限のリソースがないため、クエリが実際にアクセスするデータに述語ロックをかけるのは事実ですが、「データ」は「返された行」以上のものを意味する場合があります。

PostgreSQLは、問題があると判断した場合ではなく、問題があると判断した場合に、シリアル化の失敗にフラグを立てることを選択します。(したがって、行ロックをページロックに一般化する方法です。)この設計上の選択により、例のような誤検知が発生します。誤検知は理想的とは言えませんが、分離セマンティクスの正確性には影響しません。

エラーメッセージは次のとおりです。

ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.

そのヒントが鍵となります。アプリケーションは、シリアル化の失敗をキャッチし、操作全体を再試行する必要があります。これは、実行中の場合SERIALIZABLEは常に当てはまります。同時実行性にもかかわらずシリアルの正確性を保証しますが、アプリケーションの助けがなければそれを行うことはできません。言い換えると、実際に同時変更を行っている場合、PostgreSQLが分離要件を満たすことができる唯一の方法は、アプリケーションにそれ自体をシリアル化するように依頼することです。したがって:

この手法を使用する環境では、シリアル化の失敗を処理する一般的な方法(常にSQLSTATE値「40001」で返される)を使用することが重要です。これは、どのトランザクションが読み取り/書き込みに寄与するかを正確に予測することが非常に難しいためです。依存関係があり、シリアル化の異常を防ぐためにロールバックする必要があります。

于 2012-10-11T14:41:31.443 に答える