あなたが唯一のユーザーである場合、クエリは問題ないはずです。特に、クエリ自体の内部(外部クエリとサブクエリの間)に競合状態やデッドロックはありません。ここでマニュアルを引用します:
ただし、トランザクションがそれ自体と競合することはありません。
同時使用の場合、問題はより複雑になる可能性があります。SERIALIZABLE
あなたはトランザクションモードで安全な側にいるでしょう:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
RETURNING *
COMMIT;
このような場合は、シリアル化の失敗に備えてクエリを再試行する必要があります。
しかし、これがやり過ぎではないかどうかは完全にはわかりません。@kgrittnに立ち寄ってもらいます..彼は並行性とシリアル化可能なトランザクションのエキスパートです..
そして彼はそうしました。:)
両方の長所
デフォルトのトランザクションモードでクエリを実行しますREAD COMMITTED
。
Postgres 9.5以降の場合は、を使用しますFOR UPDATE SKIP LOCKED
。見る:
古いバージョンの場合computed IS NULL
は、外側の条件を明示的に再確認してUPDATE
ください。
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
AND computed IS NULL;
@kgrittnが彼の回答へのコメントでアドバイスしたように、このクエリは、(ありそうもない)同時トランザクションと絡み合った場合に、何もしなくても空になる可能性があります。
したがって、トランザクションモードの最初のバリアントとほぼ同じように機能しSERIALIZABLE
、パフォーマンスを低下させることなく再試行する必要があります。
唯一の問題:機会のウィンドウが非常に小さいため、競合が発生する可能性は非常に低いですが、負荷が高い場合に発生する可能性があります。最終的に行が残っていないかどうかはわかりません。
それが問題ではない場合(あなたの場合のように)、ここで完了です。
もしそうなら、絶対に確実に、空の結果を得た後、明示的なロックでもう1つのクエリを開始します。これが空になったら、完了です。そうでない場合は、続行します。plpgsql
では次のようになります。
LOOP
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL
LIMIT 1 FOR UPDATE SKIP LOCKED); -- pg 9.5+
-- WHERE id = (SELECT id FROM stuff WHERE computed IS NULL LIMIT 1)
-- AND computed IS NULL; -- pg 9.4-
CONTINUE WHEN FOUND; -- continue outside loop, may be a nested loop
UPDATE stuff
SET computed = 'working'
WHERE id = (SELECT id FROM stuff WHERE computed IS NULL
LIMIT 1 FOR UPDATE);
EXIT WHEN NOT FOUND; -- exit function (end)
END LOOP;
これにより、パフォーマンスと信頼性の両方の長所が得られるはずです。