Ihre Optionen sind:
-
Führen Sie
SERIALIZABLE
aus Isolation. Interdependente Transaktionen werden beim Festschreiben abgebrochen, da sie einen Serialisierungsfehler aufweisen. Sie werden viel Fehlerprotokoll-Spam erhalten und viele Wiederholungen unternehmen, aber es wird zuverlässig funktionieren. -
Definieren Sie einen
UNIQUE
einschränken und bei einem Fehler erneut versuchen, wie Sie bemerkt haben. Gleiche Probleme wie oben. -
Wenn es ein übergeordnetes Objekt gibt, können Sie
SELECT ... FOR UPDATE
das übergeordnete Objekt, bevor Sie Ihrmax
ausführen Anfrage. In diesem Fall würden SieSELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE
. Sie verwendenbar
als Sperre für allefoo
s mit dieserbar_id
. Sie können dann sicher sein, dass Sie fortfahren können, solange jede Abfrage, die Ihren Zähler erhöht, dies zuverlässig tut. Das kann ganz gut funktionieren.Dies führt immer noch eine aggregierte Abfrage für jeden Aufruf durch, was (für die nächste Option) unnötig ist, aber zumindest nicht das Fehlerprotokoll wie die obigen Optionen spammt.
-
Verwenden Sie einen Tresentisch. Das würde ich tun. Entweder in
bar
, oder in einer Seitentabelle wiebar_foo_counter
, erhalten Sie eine Zeilen-ID mitUPDATE bar_foo_counter SET counter = counter + 1 WHERE bar_id = $1 RETURNING counter
oder die weniger effiziente Option, wenn Ihr Framework
RETURNING
nicht verarbeiten kann :SELECT counter FROM bar_foo_counter WHERE bar_id = $1 FOR UPDATE; UPDATE bar_foo_counter SET counter = $1;
Dann in derselben Transaktion verwenden Sie die generierte Zählerzeile für die
number
. Beim Festschreiben die Zählertabellenzeile für diesebar_id
wird für die nächste zu verwendende Abfrage entsperrt. Bei einem Rollback wird die Änderung verworfen.
Ich empfehle den Counter-Ansatz, indem ich einen dedizierten Seitentisch für den Counter verwende, anstatt eine Spalte zu bar
hinzuzufügen . Das ist sauberer zu modellieren und bedeutet, dass Sie weniger Update-Bloat in bar
erzeugen , was Abfragen an bar
verlangsamen kann .