Ich muss eine eindeutige (pro Zeile) Revisionsnummer in einer document_revisions-Tabelle behalten, in der die Revisionsnummer für ein Dokument gilt, sodass sie nicht für die gesamte Tabelle, sondern nur für das zugehörige Dokument eindeutig ist.
Ich habe mir anfangs etwas ausgedacht wie:
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
Aber es gibt eine Rennbedingung!
Ich versuche es mit zu lösen pg_advisory_lock
, aber die Dokumentation ist etwas knapp und ich verstehe sie nicht vollständig und ich möchte nicht versehentlich etwas sperren.
Ist das Folgende akzeptabel oder mache ich es falsch oder gibt es eine bessere Lösung?
SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);
Sollte ich nicht stattdessen die Dokumentzeile (Schlüssel1) für eine bestimmte Operation (Schlüssel2) sperren? Das wäre also die richtige Lösung:
SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;
Vielleicht bin ich nicht an PostgreSQL gewöhnt und eine SERIAL kann einen Gültigkeitsbereich haben, oder vielleicht eine Sequenz und nextval()
würde den Job besser machen?
quelle
Antworten:
Angenommen, Sie speichern alle Revisionen des Dokuments in einer Tabelle, besteht ein Ansatz darin, die Revisionsnummer nicht zu speichern, sondern sie basierend auf der Anzahl der in der Tabelle gespeicherten Revisionen zu berechnen.
Es ist im Wesentlichen ein abgeleiteter Wert, den Sie nicht speichern müssen.
Eine Fensterfunktion kann verwendet werden, um die Revisionsnummer zu berechnen, so etwas wie
und Sie benötigen eine Spalte
change_date
, um die Reihenfolge der Revisionen zu verfolgen.Wenn Sie jedoch nur
revision
eine Eigenschaft des Dokuments haben und angeben, "wie oft sich das Dokument geändert hat", würde ich mich für den optimistischen Sperransatz entscheiden, etwa:Wenn dies 0 Zeilen aktualisiert, wurde eine Zwischenaktualisierung durchgeführt, und Sie müssen den Benutzer darüber informieren.
Versuchen Sie im Allgemeinen, Ihre Lösung so einfach wie möglich zu halten. In diesem Fall von
update
Anweisung anstelle einerselect
gefolgt von eineminsert
oderupdate
quelle
SEQUENCE ist garantiert eindeutig und Ihr Anwendungsfall sieht anwendbar aus, wenn Ihre Anzahl von Dokumenten nicht zu hoch ist (ansonsten müssen Sie viele Sequenzen verwalten). Verwenden Sie die RETURNING-Klausel, um den Wert abzurufen, der von der Sequenz generiert wurde. Verwenden Sie beispielsweise 'A36' als document_id:
Das Verwalten der Sequenzen muss mit einiger Sorgfalt behandelt werden. Sie könnten möglicherweise eine separate Tabelle behalten, die die Dokumentnamen und die damit verbundene Reihenfolge enthält,
document_id
auf die beim Einfügen / Aktualisieren derdocument_revisions
Tabelle verwiesen wird .quelle
Dies wird oft durch optimistisches Sperren gelöst:
Wenn das Update 0 aktualisierte Zeilen zurückgibt, haben Sie Ihr Update verpasst, weil bereits jemand anderes die Zeile aktualisiert hat.
quelle
(Ich bin auf diese Frage gekommen, als ich versucht habe, einen Artikel zu diesem Thema erneut zu entdecken. Nachdem ich ihn gefunden habe, veröffentliche ich ihn hier, falls andere nach einer alternativen Option für die aktuell ausgewählte Antwort suchen - Fensterung mit
row_number()
)Ich habe den gleichen Anwendungsfall. Für jeden Datensatz, der in ein bestimmtes Projekt in unserem SaaS eingefügt wird, benötigen wir eine eindeutige, inkrementelle Nummer, die angesichts gleichzeitiger
INSERT
s generiert werden kann und idealerweise lückenlos ist.Dieser Artikel beschreibt eine schöne Lösung , die ich hier zur Vereinfachung und Nachwelt zusammenfassen werde.
document_id
undcounter
.counter
wirdDEFAULT 0
alternativ, wenn Sie bereits einedocument
Entität haben, die alle Versionen gruppiert,counter
könnte dort eine hinzugefügt werden.BEFORE INSERT
derdocument_versions
Tabelle einen Trigger hinzu, der den Zähler (UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
) atomar erhöht und dannNEW.version
auf diesen Zählerwert setzt.Alternativ können Sie möglicherweise einen CTE verwenden, um dies auf der Anwendungsebene zu tun (obwohl ich es aus Gründen der Konsistenz als Auslöser bevorzuge):
Dies ähnelt im Prinzip dem Versuch, es zu lösen, mit der Ausnahme, dass durch Ändern einer Zählerzeile in einer einzelnen Anweisung Lesevorgänge des veralteten Werts blockiert werden, bis der
INSERT
festgeschrieben wird.Hier ist eine Abschrift davon,
psql
wie dies in Aktion gezeigt wird:Wie Sie sehen können, müssen Sie vorsichtig sein, wie es
INSERT
passiert, daher die Trigger-Version, die so aussieht:Das macht
INSERT
s viel einfacher und die Integrität der Daten robuster gegenüberINSERT
s, die aus beliebigen Quellen stammen:quelle