जहां तक मुझे पता है, इसे सीधे 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
में "टिड स्कैन" नोड की तलाश करके बता सकते हैं कि यह काम कर रहा है या नहीं आउटपुट)। एक ही UPDATE
के भीतर अधिक जटिल प्रश्नों को संभालने के लिए कथन, उदा. अगर आपका नया बैलेंस some_function()
by द्वारा लौटाया जा रहा था आईडी के साथ, आपको आईडी-आधारित लुकअप पर वापस जाना होगा:
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
$$