Mat und Erwin haben beide Recht, und ich füge nur eine weitere Antwort hinzu, um das, was sie gesagt haben, in einer Weise weiter zu erläutern, die nicht in einen Kommentar passt. Da ihre Antworten nicht alle zufrieden zu stellen scheinen und es einen Vorschlag gab, die PostgreSQL-Entwickler zu konsultieren, und ich bin einer, werde ich näher darauf eingehen.
Der wichtige Punkt hierbei ist, dass nach dem SQL-Standard innerhalb einer Transaktion, die auf der READ COMMITTED
Transaktionsisolationsstufe ausgeführt wird, die Einschränkung besteht, dass die Arbeit nicht festgeschriebener Transaktionen nicht sichtbar sein darf. Wann die Arbeit festgeschriebener Transaktionen sichtbar wird, hängt von der Implementierung ab. Sie weisen darauf hin, dass sich zwei Produkte dafür entschieden haben, dies umzusetzen. Keine der Implementierungen verstößt gegen die Anforderungen des Standards.
Folgendes passiert in PostgreSQL im Detail:
S1-1 läuft (1 Zeile gelöscht)
Die alte Zeile bleibt bestehen, da S1 möglicherweise immer noch zurückgesetzt wird, S1 jedoch die Zeile sperrt, sodass jede andere Sitzung, die versucht, die Zeile zu ändern, darauf wartet, ob S1 festgeschrieben oder zurückgesetzt wird. Alle Lesevorgänge der Tabelle können die alte Zeile weiterhin anzeigen, es sei denn, sie versuchen, sie mit SELECT FOR UPDATE
oder zu sperren SELECT FOR SHARE
.
S2-1 läuft (ist aber gesperrt, da S1 eine Schreibsperre hat)
S2 muss nun warten, um das Ergebnis von S1 zu sehen. Wenn S1 nicht festgeschrieben, sondern zurückgesetzt würde, würde S2 die Zeile löschen. Beachten Sie, dass, wenn S1 vor dem Rollback eine neue Version eingefügt hätte, die neue Version aus Sicht einer anderen Transaktion niemals vorhanden gewesen wäre und die alte Version aus Sicht einer anderen Transaktion nicht gelöscht worden wäre.
S1-2 läuft (1 Zeile eingefügt)
Diese Reihe ist unabhängig von der alten. Wenn es eine Aktualisierung der Zeile mit id = 1 gegeben hätte, wären die alte und die neue Version miteinander verknüpft, und S2 könnte die aktualisierte Version der Zeile löschen, wenn sie entsperrt würde. Dass eine neue Zeile zufällig dieselben Werte wie eine in der Vergangenheit vorhandene Zeile hat, macht sie nicht zu einer aktualisierten Version dieser Zeile.
S1-3 wird ausgeführt und gibt die Schreibsperre frei
Die Änderungen von S1 werden also beibehalten. Eine Reihe ist weg. Eine Zeile wurde hinzugefügt.
S2-1 läuft, jetzt wo es die Sperre bekommen kann. Es werden jedoch 0 Zeilen gelöscht. HUH ???
Intern passiert, dass es einen Zeiger von einer Version einer Zeile zur nächsten Version derselben Zeile gibt, wenn diese aktualisiert wird. Wenn die Zeile gelöscht wird, gibt es keine nächste Version. Wenn eine READ COMMITTED
Transaktion bei einem Schreibkonflikt aus einem Block erwacht, folgt sie dieser Aktualisierungskette bis zum Ende. Wenn die Zeile nicht gelöscht wurde und die Auswahlkriterien der Abfrage noch erfüllt, wird sie verarbeitet. Diese Zeile wurde gelöscht, sodass die Abfrage von S2 fortgesetzt wird.
S2 kann während seines Abtastens der Tabelle zu der neuen Zeile gelangen oder nicht. Wenn dies der Fall ist, wird angezeigt, dass die neue Zeile nach dem DELETE
Start der Anweisung von S2 erstellt wurde und daher nicht Teil des für sie sichtbaren Zeilensatzes ist.
Wenn PostgreSQL die gesamte DELETE-Anweisung von S2 von Anfang an mit einem neuen Snapshot neu starten würde, würde sie sich genauso verhalten wie SQL Server. Die PostgreSQL-Community hat dies aus Performancegründen nicht gewählt. In diesem einfachen Fall würden Sie den Unterschied in der Leistung nie bemerken, aber wenn Sie zehn Millionen Zeilen in einer DELETE
Zeile wären, als Sie blockiert wurden, würden Sie dies sicherlich tun. Hier gibt es einen Kompromiss, bei dem PostgreSQL die Leistung gewählt hat, da die schnellere Version immer noch den Anforderungen des Standards entspricht.
S2-2 wird ausgeführt und meldet eine eindeutige Verletzung der Schlüsselbedingung
Natürlich ist die Zeile bereits vorhanden. Dies ist der am wenigsten überraschende Teil des Bildes.
Obwohl es hier ein überraschendes Verhalten gibt, stimmt alles mit dem SQL-Standard überein und liegt im Rahmen dessen, was gemäß dem Standard "implementierungsspezifisch" ist. Es kann sicherlich überraschend sein, wenn Sie davon ausgehen, dass das Verhalten einer anderen Implementierung in allen Implementierungen vorhanden ist. PostgreSQL versucht jedoch, Serialisierungsfehler in der READ COMMITTED
Isolationsstufe zu vermeiden , und lässt einige Verhaltensweisen zu, die sich von anderen Produkten unterscheiden, um dies zu erreichen.
Ich persönlich bin kein großer Fan der READ COMMITTED
Transaktionsisolationsstufe in der Implementierung eines Produkts. Sie alle ermöglichen es den Rennbedingungen, aus transaktionaler Sicht überraschende Verhaltensweisen hervorzurufen. Sobald sich jemand an die seltsamen Verhaltensweisen eines Produkts gewöhnt, neigt er dazu, das "Normale" und die von einem anderen Produkt gewählten Kompromisse für ungerade zu halten. Aber jedes Produkt muss einen Kompromiss für einen Modus eingehen, der nicht als implementiert ist SERIALIZABLE
. Die PostgreSQL-Entwickler haben sich für die READ COMMITTED
Minimierung der Blockierung (Lesevorgänge blockieren keine Schreibvorgänge und Schreibvorgänge blockieren keine Lesevorgänge) und die Minimierung der Wahrscheinlichkeit von Serialisierungsfehlern entschieden.
Der Standard verlangt, dass SERIALIZABLE
Transaktionen die Standardeinstellung sind, aber die meisten Produkte tun dies nicht, da dies zu Leistungseinbußen in Bezug auf die laxeren Transaktionsisolationsstufen führt. Einige Produkte bieten bei SERIALIZABLE
Auswahl nicht einmal wirklich serialisierbare Transaktionen - insbesondere Oracle und Versionen von PostgreSQL vor 9.1. Die Verwendung von echten SERIALIZABLE
Transaktionen ist jedoch die einzige Möglichkeit, um überraschende Auswirkungen von Rennbedingungen zu vermeiden, und SERIALIZABLE
Transaktionen müssen immer entweder blockiert werden, um die Rennbedingungen zu vermeiden, oder einige Transaktionen zurückgesetzt werden, um eine sich entwickelnde Rennbedingung zu vermeiden. Die häufigste Implementierung von SERIALIZABLE
Transaktionen ist das strikte Zwei-Phasen-Sperren (S2PL), bei dem sowohl Blockierungs- als auch Serialisierungsfehler (in Form von Deadlocks) auftreten.
Vollständige Offenlegung: Ich habe mit Dan Ports vom MIT zusammengearbeitet, um PostgreSQL Version 9.1 mithilfe einer neuen Technik namens Serializable Snapshot Isolation wirklich serialisierbare Transaktionen hinzuzufügen.
READ COMMITTED
Transaktionen verwenden, besteht eine Race-Bedingung: Was würde passieren, wenn eine andere Transaktion nach dem erstenDELETE
und vor dem zweitenDELETE
Start eine neue Zeile einfügt ? Bei Transaktionen weniger streng alsSERIALIZABLE
die beiden wichtigsten Möglichkeiten , um enge Rennbedingungen sind durch Förderung eines Konflikts (aber das hilft nicht , wenn die Zeile gelöscht wird) und Materialisierung eines Konflikts. Sie können den Konflikt materialisieren, indem Sie für jede gelöschte Zeile eine "id" -Tabelle aktualisieren oder die Tabelle explizit sperren. Oder verwenden Sie Wiederholungsversuche bei Fehlern.Ich glaube , das ist von Entwurf, nach der Beschreibung der Lese-engagierte Isolationsstufe für PostgreSQL 9.2:
Die Zeile, in
S1
die Sie einfügenS2
, war zuDELETE
Beginn noch nicht vorhanden . Es wird also nicht durch das Löschen inS2
gemäß ( 1 ) oben gesehen. DasS1
gelöschte wird vonS2
'sDELETE
gemäß ( 2 ) .Also
S2
, tut das Löschen nichts. Wenn der Einsatz allerdings kommt, dass man tut sehenS1
ist , einfügen:Also das versuchte Einfügen von
S2
schlägt mit der Einschränkungsverletzung fehl.Lesen Sie das Dokument weiter, verwenden Sie wiederholbares Lesen oder können Sie es sogar serialisieren würde Ihr Problem nicht vollständig lösen - die zweite Sitzung würde mit einem Serialisierungsfehler beim Löschen fehlschlagen.
Auf diese Weise können Sie die Transaktion erneut versuchen.
quelle
Ich stimme der hervorragenden Antwort von @ Mat voll und ganz zu . Ich schreibe nur eine andere Antwort, weil sie nicht in einen Kommentar passt.
Als Antwort auf Ihren Kommentar:
DELETE
in S2 ist bereits an eine bestimmte Zeilenversion angebunden. Da dies zwischenzeitlich von S1 getötet wird, sieht sich S2 als erfolgreich. Obwohl auf einen kurzen Blick nicht ersichtlich, sieht die Veranstaltungsreihe praktisch so aus:Es ist alles beabsichtigt. Sie müssen wirklich
SERIALIZABLE
Transaktionen für Ihre Anforderungen verwenden und sicherstellen, dass Sie es bei einem Serialisierungsfehler erneut versuchen.quelle
Verwenden Sie einen DEFERRABLE- Primärschlüssel und versuchen Sie es erneut.
quelle
Wir waren auch mit diesem Problem konfrontiert. Unsere Lösung fügt
select ... for update
vordelete from ... where
. Die Isolationsstufe muss Read Commit sein.quelle