PGQなど、この問題を既に解決しているツールを調べることを強くお勧めします。待ち行列は予想以上に大変です。これは、再発明したい車輪ではありません。
同時実行は難しい
Mihai の答えは表面的には問題ないように見えますが、同時操作ではやや落ちます。
2 つの同時 UPDATE で同じ行を選択できます (この例では がありますused_flag = FALSE
)。そのうちの 1 つがロックを取得して続行します。もう1つは、最初の実行とコミットまで待機します。コミットが発生すると、2 回目の更新でロックが取得され、その状態が再チェックされ、一致する行が見つからなくなり、何も実行されません。したがって、一連の同時更新の 1 つを除くすべてが空のセットを返す可能性があります (実際には非常に可能性が高い)。
READ COMMITTED
モードでもまともな結果を得ることができます。これは、単一のセッションが継続的にループするのとほぼ同じですUPDATE
。モードでは絶望的にSERIALIZABLE
失敗します。それを試してみてください; セットアップは次のとおりです。
CREATE TABLE paths (
used_flag boolean not null default 'f',
when_entered timestamptz not null default current_timestamp,
data text not null
);
INSERT INTO paths (data) VALUES
('aa'),('bb'),('cc'),('dd');
これがデモです。ステップバイステップに従って、3 つの同時セッションで試してください。READ COMMITTED で一度実行してから、プレーンの代わりにSERIALIZABLE
使用してすべてのセッションでもう一度実行します。結果を比較します。BEGIN ISOLATION LEVEL SERIALIZABLE
BEGIN
SESSION 1 SESSION2 SESSION 3
BEGIN;
BEGIN;
UPDATE paths
SET used_flag = TRUE
WHERE used_flag = FALSE
RETURNING data;
BEGIN;
INSERT INTO
paths(data)
VALUES
('ee'),('ff');
COMMIT;
UPDATE paths
SET used_flag = TRUE
WHERE used_flag = FALSE
RETURNING data;
BEGIN;
INSERT INTO
paths(data)
VALUES
('gg'),('hh');
COMMIT;
COMMIT;
最初の UPDATEはREAD COMMITTED
成功し、4 つの行が生成されます。2 番目は、最初の更新の実行後に挿入およびコミットされた残りの 2 つのee
andを生成します。コミット後に実際に実行されても、2 回目の更新では返されません。これは、行が既に選択されており、挿入されるまでにロックを待機しているためです。ff
gg
hh
単独ではSERIALIZABLE
、最初の UPDATE が成功し、4 つの行が生成されます。2 番目は で失敗しERROR: could not serialize access due to concurrent update
ます。この場合、SERIALIZABLE
分離は役に立たず、失敗の性質を変えるだけです。
明示的なトランザクションがなければ、s が同時に実行されたときに同じことが起こりUPDATE
ます。明示的なトランザクションを使用すると、タイミングをいじることなく簡単にデモを行うことができます。
1 行だけを選択する場合はどうでしょうか。
上記のように、システムは問題なく動作しますが、最も古い行のみを取得したい場合はどうすればよいでしょうか? は、ロックでブロックする前に操作対象の行を選択するため、特定の一連のトランザクションで 1 つのみが結果を返すUPDATE
ことがわかります。UPDATE
次のようなトリックを考えます。
UPDATE paths
SET used_flag = TRUE
WHERE entry_id = (
SELECT entry_id
FROM paths
WHERE used_flag = FALSE
ORDER BY when_entered
LIMIT 1
)
AND used_flag = FALSE
RETURNING data;
また
UPDATE paths
SET used_flag = TRUE
WHERE entry_id = (
SELECT min(entry_id)
FROM paths
WHERE used_flag = FALSE
)
AND used_flag = FALSE
RETURNING data;
しかし、これらは期待どおりには機能しません。同時に実行すると、両方が同じターゲット行を選択します。1 つは続行し、1 つは最初のコミットまでロックでブロックし、次に続行して空の結果を返します。2番目がなければ、AND used_flag = FALSE
重複を返すことさえできると思います! 上記のデモ テーブルentry_id SERIAL PRIMARY KEY
に列を追加してから試してください。paths
彼らをレースにLOCK TABLE paths
参加させるには、3回目のセッションで。次のリンクにある例を参照してください。
これらの問題について別の回答で書きました。私の回答では、複数のスレッドが制約付きセットで重複した更新を引き起こす可能性があります。
真剣に、PGQをチェックしてください。これはすでに解決されています。