2

私はPostgreSQL(おそらくpsqlだけでなく)のトランザクション競合状態の問題に遭遇しました。私は複数のスレッドを使用してこのような単純なタスクを達成しようとしている:SELECT FOR UPDATE間違った結果

BEGIN; 
SELECT * FROM t WHERE id = 1; 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,.. 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

のスレッドがその複数の挿入が実行されているこれらの取引(主キーの衝突エラー)の内側に衝突しているが。だから私は、SELECT FOR UPDATE文を使用しようとしました:

BEGIN; 
SELECT * FROM t WHERE id = 1 FOR UPDATE; 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,.. 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

取引は正しく、他のスレッドがコミットを待っFOR UPDATE文でブロックしています。

しかし「アップセマフォ」の後に(他のスレッドのトランザクションがコミットした後に、その文に目を覚ます)データがテーブルに正しくご利用いただけますが、空の結果セットが(速くスレッドからINSERT文から)DBMSから返されます。

BEGIN; 
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- blocking ... then return 0 records WRONG 
SELECT * FROM t WHERE id = 1 FOR UPDATE; -- second try ... returns 1 record CORRECT 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread X'); -- X = 1,2,3,.. 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

上記のように、2番目の(重複した)select文は正しく動作します。どうして?

答えて

0

理由は、ブロックされたステートメントのスナップショットが、新しい行を挿入したトランザクションよりも古いため、ロックが解除されてもそれを見ることができないためです。

READ COMMITTED分離レベルでは各ステートメントに独自のスナップショットが設定されているため、2番目のステートメントのスナップショットに新しく挿入された行が含まれているため、次のステートメントで確認できます。

REPEATABLE READ分離レベルを使用できます。その場合は、シリアル化エラーが発生するはずです(私はそれをテストしなかったので、–を試してみてください。おそらくSERIALIZABLEが必要です)。次に、シリアル化エラーが発生した場合にトランザクションを再試行するようにプログラムを記述しなければなりません。