I det relaterede svar henviser du til:
- Postgres OPDATERING ... GRÆNSE 1
Målet er at låse en række ad gangen. Dette fungerer fint med eller uden rådgivende låse, for der er ingen chance for dødvande - så længe du ikke forsøger at låse flere rækker i samme transaktion.
Dit eksempel er anderledes ved, at du vil låse 3000 rækker ad gangen . Der er potentiale for deadlock, undtagen hvis alle samtidige skriveoperationer låser rækker i samme konsistente rækkefølge. Per dokumentation:
Det bedste forsvar mod deadlocks er generelt at undgå dem ved at være sikker på, at alle applikationer, der bruger en database, får låse på flere objekter i en konsistent rækkefølge.
Implementer det med en ORDER BY i din underforespørgsel.
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;
Dette er sikkert og pålideligt, så længe alle transaktioner får låse i samme rækkefølge, og samtidige opdateringer af bestillingskolonnerne kan ikke forventes. (Læs den gule "FORSIGTIG"-boks i slutningen af dette kapitel i manualen.) Så dette burde være sikkert i dit tilfælde, da du ikke kommer til at opdatere id
kolonne.
Faktisk kan kun én klient ad gangen manipulere rækker på denne måde. Samtidige transaktioner ville forsøge at låse de samme (låste) rækker og vente på, at den første transaktion er færdig.
Rådgivende låse er nyttige, hvis du har mange eller meget langvarige samtidige transaktioner (det ser det ikke ud til, at du har). Med kun få vil det generelt være billigere blot at bruge ovenstående forespørgsel og lade samtidige transaktioner vente på deres tur.
Alt i én OPDATERING
Det ser ud til, at samtidig adgang ikke er et problem i sig selv i din opsætning. Samtidighed er et problem skabt af din nuværende løsning.
Gør det i stedet alt i en enkelt UPDATE
. Tildel batcher af n
numre (3000 i eksemplet) til hver UUID og opdatere alle på én gang. Bør være hurtigst.
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;
Vigtige punkter
-
Heltalsdivision afkortes. Du får 1 for de første 3000 rækker, 2 for de næste 3000 rækker. osv.
-
Jeg vælger rækker vilkårligt, du kan anvende
ORDER BY
i vinduet forrow_number()
for at tildele bestemte rækker. -
Hvis du ikke har en tabel over UUID'er at sende (
uuid_tbl
), brug enVALUES
udtryk for at levere dem. Eksempel. -
Du får partier på 3000 rækker. Den sidste batch mangler 3000, hvis du ikke finder et multiplum af 3000 at tildele.