In der zugehörigen Antwort beziehen Sie sich auf:
- Postgres-UPDATE ... LIMIT 1
Ziel ist es, einen zu sperren Reihe auf einmal. Dies funktioniert gut mit oder ohne Advisory Locks, da es keine Chance für einen Deadlock gibt - solange Sie nicht versuchen, weitere Zeilen in derselben Transaktion zu sperren.
Ihr Beispiel unterscheidet sich darin, dass Sie 3000 Zeilen gleichzeitig sperren möchten . Es ist Potenzial für Deadlocks, es sei denn, alle gleichzeitigen Schreibvorgänge sperren Zeilen in derselben konsistenten Reihenfolge. Per Dokumentation:
Die beste Verteidigung gegen Deadlocks besteht im Allgemeinen darin, sie zu vermeiden, indem sichergestellt wird, dass alle Anwendungen, die eine Datenbank verwenden, Sperren für mehrere Objekte in einer konsistenten Reihenfolge erwerben.
Implementieren Sie das mit einem ORDER BY in Ihrer Unterabfrage.
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;
Dies ist sicher und zuverlässig, solange alle Transaktionen erwerben Sperren in derselben Reihenfolge, und gleichzeitige Aktualisierungen der Reihenfolgespalten sind nicht zu erwarten. (Lesen Sie den gelben „VORSICHT“-Kästchen am Ende dieses Kapitels im Handbuch.) Dies sollte also in Ihrem Fall sicher sein, da Sie die id
nicht aktualisieren werden Spalte.
Effektiv kann jeweils nur ein Client Zeilen auf diese Weise manipulieren. Gleichzeitige Transaktionen würden versuchen, dieselben (gesperrten) Zeilen zu sperren und warten, bis die erste Transaktion abgeschlossen ist.
Hinweissperren sind nützlich, wenn Sie viele oder sehr lange gleichzeitige Transaktionen haben (scheint nicht zu sein). Bei nur wenigen ist es insgesamt billiger, einfach die obige Abfrage zu verwenden und gleichzeitige Transaktionen warten zu lassen, bis sie an der Reihe sind.
Alles in einem UPDATE
Es scheint, dass der gleichzeitige Zugriff in Ihrem Setup per se kein Problem darstellt. Nebenläufigkeit ist ein Problem, das durch Ihre aktuelle Lösung verursacht wird.
Machen Sie stattdessen alles in einem einzigen UPDATE
. Weisen Sie Chargen von n
zu Nummern (3000 im Beispiel) zu jeder UUID und aktualisieren Sie alle auf einmal. Sollte am schnellsten sein.
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;
Wichtige Punkte
-
Ganzzahldivision schneidet ab. Sie erhalten 1 für die ersten 3000 Zeilen, 2 für die nächsten 3000 Zeilen. usw.
-
Ich wähle Zeilen willkürlich aus, Sie könnten
ORDER BY
anwenden im Fenster fürrow_number()
um bestimmte Zeilen zuzuweisen. -
Wenn Sie keine Tabelle mit zu versendenden UUIDs haben (
uuid_tbl
), verwenden Sie einenVALUES
Ausdruck, um sie zu liefern. Beispiel. -
Sie erhalten Stapel von 3000 Zeilen. Der letzte Stapel wird knapp 3000 sein, wenn Sie kein Vielfaches von 3000 finden, das Sie zuweisen können.