Ich verstehe nicht, was Craig Ringer meinte, als er kommentierte:
Diese Lösung kann verloren gehen, wenn die Einfügetransaktion zurückgesetzt wird. Es wird nicht überprüft, ob das UPDATE Zeilen betrifft.
unter https://stackoverflow.com/a/8702291/14731 . Bitte geben Sie eine Beispielsequenz von Ereignissen an (z. B. Thread 1 macht X, Thread 2 macht Y), die zeigt, wie verlorene Updates auftreten können.
postgresql
concurrency
cte
upsert
Gili
quelle
quelle
Antworten:
Ich denke, ich wollte diesen Kommentar wahrscheinlich zur vorherigen Antwort hinzufügen, zu zwei getrennten Aussagen. Es war vor über einem Jahr, also bin ich mir nicht mehr ganz sicher.
Die wCTE-basierte Abfrage löst das Problem nicht wirklich, aber wenn ich sie über ein Jahr später erneut überprüfe, sehe ich keine Möglichkeit, dass Updates in der wCTE-Version verloren gehen.
(Beachten Sie, dass alle diese Lösungen nur dann gut funktionieren, wenn Sie versuchen, mit jeder Transaktion genau eine Zeile zu ändern. Sobald Sie versuchen, mehrere Änderungen in einer Transaktion vorzunehmen, werden die Dinge unordentlich, da bei Rollbacks Wiederholungsschleifen erforderlich sind. Zumindest Sie müssten zwischen jeder Änderung einen Sicherungspunkt verwenden.)
Version mit zwei Anweisungen, vorbehaltlich verlorener Aktualisierungen.
Die Version, die zwei separate Anweisungen verwendet, unterliegt verlorenen Aktualisierungen, es sei denn, die Anwendung überprüft die Anzahl der betroffenen Zeilen aus der
UPDATE
Anweisung und derINSERT
Anweisung und versucht es erneut, wenn beide Null sind.Stellen Sie sich vor, was passiert, wenn Sie zwei Transaktionen
READ COMMITTED
isoliert haben.UPDATE
(kein Effekt)INSERT
(fügt eine Zeile ein)UPDATE
(kein Effekt, von TX1 eingefügte Zeile ist noch nicht sichtbar)COMMIT
s.INSERT
, * aus, das einen neuen Snapshot erhält, der die von TX1 festgeschriebene Zeile sehen kann. DieEXISTS
Klausel gibt true zurück, da TX2 jetzt die von TX1 eingefügte Zeile sehen kann.TX2 hat also keine Wirkung. Wenn die App nicht die Zeilenanzahl aus dem Update und dem Einfügen überprüft und erneut versucht, wenn beide keine Zeilen melden, weiß sie nicht, dass die Transaktion keine Auswirkungen hatte, und wird fröhlich fortgesetzt.
Die einzige Möglichkeit, die betroffenen Zeilenzahlen zu überprüfen, besteht darin, sie als zwei separate Anweisungen anstatt als Mehrfachanweisung auszuführen oder eine Prozedur zu verwenden.
Sie können die
SERIALIZABLE
Isolation verwenden, benötigen jedoch noch eine Wiederholungsschleife, um Serialisierungsfehler zu beheben.Die wCTE-Version schützt vor dem Problem mit verlorenen Updates, da dies davon
INSERT
abhängt, ob dieUPDATE
Zeilen betroffen sind, und nicht von einer separaten Abfrage.Der wCTE beseitigt keine eindeutigen Verstöße
Die beschreibbare CTE-Version ist immer noch kein zuverlässiger Upsert.
Betrachten Sie zwei Transaktionen, die dies gleichzeitig ausführen.
Beide führen die VALUES-Klausel aus.
Jetzt führen beide den
UPDATE
Teil aus. Da es keine Zeilen gibt, die mit derUPDATE
s where-Klausel übereinstimmen , geben beide eine leere Ergebnismenge aus dem Update zurück und nehmen keine Änderungen vor.Jetzt führen beide die
INSERT
Portion aus. DaUPDATE
für beide Abfragen keine Zeilen zurückgegeben wurden, versuchen beide,INSERT
die Zeile abzurufen.Man schafft es. Man wirft eine einzigartige Verletzung und bricht ab.
Dies ist kein Grund zur Besorgnis über Datenverlust, solange die App auf Fehlerergebnisse aus ihren Abfragen (dh jeder anständig geschriebenen App) prüft und es erneut versucht. Die Lösung ist jedoch nicht besser als die vorhandenen Versionen mit zwei Anweisungen. Eine Wiederholungsschleife ist nicht erforderlich.
Der Vorteil, den der wCTE gegenüber der vorhandenen Version mit zwei Anweisungen bietet, besteht darin, dass er anhand der Ausgabe von
UPDATE
entscheidet, ob dies der Fall istINSERT
, anstatt eine separate Abfrage für die Tabelle zu verwenden. Dies ist teilweise eine Optimierung, schützt jedoch teilweise vor einem Problem mit der Version mit zwei Anweisungen, das zu verlorenen Updates führt. siehe unten.Sie können den wCTE
SERIALIZABLE
isoliert ausführen , aber dann erhalten Sie nur Serialisierungsfehler anstelle eindeutiger Verstöße. Die Notwendigkeit einer Wiederholungsschleife wird dadurch nicht geändert.Der wCTE scheint nicht anfällig für verlorene Updates zu sein
Mein Kommentar deutete darauf hin, dass diese Lösung zu verlorenen Updates führen könnte, aber nach Überprüfung, dass ich mich möglicherweise geirrt habe.
Es ist über ein Jahr her und ich kann mich nicht an die genauen Umstände erinnern, aber ich glaube, ich habe wahrscheinlich die Tatsache übersehen, dass eindeutige Indizes eine teilweise Ausnahme von den Regeln für die Sichtbarkeit von Transaktionen aufweisen, damit eine einfügende Transaktion auf das Einfügen oder Rollen einer anderen warten kann zurück, bevor Sie fortfahren.
Oder vielleicht habe ich die Tatsache übersehen, dass das
INSERT
im wCTE davon abhängt, ob dieUPDATE
betroffenen Zeilen betroffen sind, nicht davon, ob die Kandidatenzeile in der Tabelle vorhanden ist.Konflikte
INSERT
mit einem eindeutigen Index warten auf Commit / RollbackAngenommen, eine Kopie der Abfrage wird ausgeführt und eine Zeile eingefügt. Die Änderung ist noch nicht festgeschrieben. Das neue Tupel ist im Heap und im eindeutigen Index vorhanden, für andere Transaktionen jedoch unabhängig von der Isolationsstufe noch nicht sichtbar.
Nun wird eine weitere Kopie der Abfrage ausgeführt. Die eingefügte Zeile ist noch nicht sichtbar, da die erste Kopie nicht festgeschrieben wurde und das Update daher mit nichts übereinstimmt. Bei der Abfrage wird eine Einfügung versucht, bei der festgestellt wird, dass eine andere laufende Transaktion denselben Schlüssel einfügt, und das Warten auf das Festschreiben oder Zurücksetzen dieser Transaktion blockiert wird .
Wenn die erste Transaktion festgeschrieben wird, schlägt die zweite Transaktion mit einer eindeutigen Verletzung fehl, wie oben beschrieben. Wenn die erste Transaktion zurückgesetzt wird, wird die zweite stattdessen eingefügt.
Die
INSERT
Abhängigkeit von derUPDATE
Zeilenanzahl schützt vor verlorenen UpdatesAnders als im Fall mit zwei Anweisungen glaube ich nicht, dass der wCTE anfällig für verlorene Updates ist.
Wenn das
UPDATE
keine Auswirkung hat,INSERT
wird das immer ausgeführt, da es streng davon abhängig ist, ob dasUPDATE
etwas getan hat, nicht vom externen Tabellenstatus. Es kann also immer noch mit einer eindeutigen Verletzung fehlschlagen, aber es kann nicht stillschweigend fehlschlagen und das Update vollständig verlieren.quelle