Sejauh yang saya tahu, tidak ada cara untuk mencapai ini secara langsung melalui UPDATE
penyataan; satu-satunya cara untuk menjamin urutan penguncian adalah dengan memperoleh kunci secara eksplisit dengan SELECT ... ORDER BY ID FOR UPDATE
, misalnya:
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
)
Ini memiliki kelemahan mengulang ID
pencarian indeks pada Balances
meja. Dalam contoh sederhana Anda, Anda dapat menghindari overhead ini dengan mengambil alamat baris fisik (diwakili oleh ctid
kolom sistem
) selama kueri penguncian, dan menggunakannya untuk mendorong 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
))
(Hati-hati saat menggunakan ctid
s, karena nilainya bersifat sementara. Kami aman di sini, karena kunci akan memblokir perubahan apa pun.)
Sayangnya, perencana hanya akan menggunakan ctid
dalam serangkaian kasus yang sempit (Anda dapat mengetahui apakah itu berfungsi dengan mencari simpul "Pemindaian Tid" di EXPLAIN
keluaran). Untuk menangani kueri yang lebih rumit dalam satu UPDATE
pernyataan, mis. jika saldo baru Anda dikembalikan oleh some_function()
di samping ID, Anda harus kembali ke pencarian berbasis 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
Jika overhead kinerja menjadi masalah, Anda harus menggunakan kursor, yang akan terlihat seperti ini:
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
$$