私の知る限り、UPDATE
を介してこれを直接達成する方法はありません。 声明;ロックの順序を保証する唯一の方法は、SELECT ... ORDER BY ID FOR UPDATE
を使用してロックを明示的に取得することです。 例:
UPDATE Balances
SET Balance = 0
WHERE ID IN (
SELECT ID FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
)
これには、ID
を繰り返すという欠点があります。 Balances
でのインデックスルックアップ テーブル。簡単な例では、物理行アドレス( ctid
システム列
)ロッククエリ中に、それを使用してUPDATE
を駆動します :
UPDATE Balances
SET Balance = 0
WHERE ctid = ANY(ARRAY(
SELECT ctid FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
))
(ctid
を使用するときは注意してください s、値は一時的なものであるため。ロックによって変更がブロックされるため、ここでは安全です。)
残念ながら、プランナーはctid
のみを使用します 限られたケースの場合(EXPLAIN
で「TidScan」ノードを探すことで機能しているかどうかを確認できます 出力)。単一のUPDATE
内でより複雑なクエリを処理するため ステートメント、例:新しい残高がsome_function()
によって返されていた場合 IDと一緒に、IDベースのルックアップにフォールバックする必要があります:
UPDATE Balances
SET Balance = Locks.NewBalance
FROM (
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE
) Locks
WHERE Balances.ID = Locks.ID
パフォーマンスのオーバーヘッドが問題になる場合は、カーソルを使用する必要があります。カーソルは次のようになります。
DO $$
DECLARE
c CURSOR FOR
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE;
BEGIN
FOR row IN c LOOP
UPDATE Balances
SET Balance = row.NewBalance
WHERE CURRENT OF c;
END LOOP;
END
$$