Warum ist CTE offen für verlorene Updates?

8

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.

Gili
quelle
1
Fragen Sie mich etwas über einen Kommentar, den ich vor über einem Jahr zu einem komplexen Thema hinterlassen habe ... Spaß! Jetzt muss ich mich daran erinnern, was genau das Problem war. Überprüfen Sie es jetzt noch einmal.
Craig Ringer

Antworten:

14

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 UPDATEAnweisung und der INSERTAnweisung und versucht es erneut, wenn beide Null sind.

Stellen Sie sich vor, was passiert, wenn Sie zwei Transaktionen READ COMMITTEDisoliert haben.

  • TX1 führt die aus UPDATE(kein Effekt)
  • TX1 führt die aus INSERT(fügt eine Zeile ein)
  • TX2 führt das aus UPDATE(kein Effekt, von TX1 eingefügte Zeile ist noch nicht sichtbar)
  • TX1 COMMITs.
  • TX2 führt das INSERT, * aus, das einen neuen Snapshot erhält, der die von TX1 festgeschriebene Zeile sehen kann. Die EXISTSKlausel 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 SERIALIZABLEIsolation 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 INSERTabhängt, ob die UPDATEZeilen 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 UPDATETeil aus. Da es keine Zeilen gibt, die mit der UPDATEs where-Klausel übereinstimmen , geben beide eine leere Ergebnismenge aus dem Update zurück und nehmen keine Änderungen vor.

  • Jetzt führen beide die INSERTPortion aus. Da UPDATEfür beide Abfragen keine Zeilen zurückgegeben wurden, versuchen beide, INSERTdie 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 UPDATEentscheidet, ob dies der Fall ist INSERT, 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 SERIALIZABLEisoliert 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 INSERTim wCTE davon abhängt, ob die UPDATEbetroffenen Zeilen betroffen sind, nicht davon, ob die Kandidatenzeile in der Tabelle vorhanden ist.

Konflikte INSERTmit einem eindeutigen Index warten auf Commit / Rollback

Angenommen, 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 INSERTAbhängigkeit von der UPDATEZeilenanzahl schützt vor verlorenen Updates

Anders als im Fall mit zwei Anweisungen glaube ich nicht, dass der wCTE anfällig für verlorene Updates ist.

Wenn das UPDATEkeine Auswirkung hat, INSERTwird das immer ausgeführt, da es streng davon abhängig ist, ob das UPDATEetwas 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.

Craig Ringer
quelle