I det relaterade svaret syftar du på:
- Postgres UPPDATERING ... GRÄNS 1
Målet är att låsa en rad åt gången. Detta fungerar bra med eller utan rådgivande lås, eftersom det ingen risk för ett dödläge - så länge du inte försöker låsa fler rader i samma transaktion.
Ditt exempel är annorlunda genom att du vill låsa 3000 rader åt gången . Det finns potential för dödläge, förutom om alla samtidiga skrivoperationer låser rader i samma konsekventa ordning. Per dokumentation:
Det bästa försvaret mot dödlägen är i allmänhet att undvika dem genom att vara säker på att alla applikationer som använder en databas får lås på flera objekt i en konsekvent ordning.
Implementera det med en ORDER BY i din underfråga.
UPDATE cargo_item item
SET job_id = 'SOME_UUID', job_ts = now()
FROM (
SELECT id
FROM cargo_item
WHERE state='NEW' AND job_id is null
ORDER BY id
LIMIT 3000
FOR UPDATE
) sub
WHERE item.id = sub.id;
Detta är säkert och pålitligt, så länge som alla transaktioner får lås i samma ordning och samtidiga uppdateringar av beställningskolumnerna är inte att förvänta. (Läs den gula "VARNING"-rutan i slutet av det här kapitlet i manualen.) Så detta bör vara säkert i ditt fall, eftersom du inte kommer att uppdatera id
kolumn.
I praktiken kan endast en klient åt gången manipulera rader på detta sätt. Samtidiga transaktioner skulle försöka låsa samma (låsta) rader och vänta på att den första transaktionen slutförs.
Rådgivningslås är användbara om du har många eller mycket långvariga samtidiga transaktioner (verkar inte göra det). Med bara ett fåtal blir det totalt sett billigare att bara använda ovanstående fråga och låta samtidigt transaktioner vänta på sin tur.
Allt i en UPPDATERING
Det verkar som om samtidig åtkomst inte är ett problem i sig i din installation. Samtidighet är ett problem som skapas av din nuvarande lösning.
Gör det istället allt i en enda UPDATE
. Tilldela batcher av n
nummer (3000 i exemplet) till varje UUID och uppdatera allt på en gång. Bör vara snabbast.
UPDATE cargo_item c
SET job_id = u.uuid_col
, job_ts = now()
FROM (
SELECT row_number() OVER () AS rn, uuid_col
FROM uuid_tbl WHERE <some_criteria> -- or see below
) u
JOIN (
SELECT (row_number() OVER () / 3000) + 1 AS rn, item.id
FROM cargo_item
WHERE state = 'NEW' AND job_id IS NULL
FOR UPDATE -- just to be sure
) c2 USING (rn)
WHERE c2.item_id = c.item_id;
Huvudpunkter
-
Heltalsdivision trunkeras. Du får 1 för de första 3000 raderna, 2 för de nästa 3000 raderna. etc.
-
Jag väljer rader godtyckligt, du kan använda
ORDER BY
i fönstret förrow_number()
för att tilldela vissa rader. -
Om du inte har en tabell med UUID att skicka (
uuid_tbl
), använd enVALUES
uttryck för att tillhandahålla dem. Exempel. -
Du får satser på 3000 rader. Den sista batchen kommer att sakna 3000 om du inte hittar en multipel av 3000 att tilldela.