Ich werde eine Antwort aus Entwicklersicht geben.
Meiner Meinung nach liegt es an einem Fehler in Ihrer Anwendung, wenn Sie auf einen Zeilenkonflikt stoßen, wie den, den Sie beschreiben. In den meisten Fällen handelt es sich bei dieser Art von Konflikten um ein Anzeichen für eine Sicherheitsanfälligkeit aufgrund eines verlorenen Updates. Dieser Thread auf AskTom erklärt das Konzept eines verlorenen Updates:
Ein verlorenes Update findet statt, wenn:
Sitzung 1: Lesen Sie Toms Mitarbeiterakte aus
Sitzung 2: Lesen Sie Toms Mitarbeiterakte aus
Sitzung 1: Aktualisierung von Toms Mitarbeiterdatensatz
Sitzung 2: Aktualisierung von Toms Mitarbeiterdatensatz
Sitzung 2 SCHREIBT die Änderungen von Sitzung 1 ÜBER, ohne sie jemals zu sehen - was zu einem verlorenen Update führt.
Sie haben eine böse Nebenwirkung des verlorenen Updates erlebt: Sitzung 2 kann blockiert werden, da Sitzung 1 noch nicht festgeschrieben wurde. Das Hauptproblem ist jedoch, dass Sitzung 2 den Datensatz blind aktualisiert. Angenommen, beide Sitzungen geben die folgende Anweisung aus:
UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk
Nach beiden Anweisungen wurden die Änderungen von Sitzung1 überschrieben, ohne dass Sitzung2 darüber informiert wurde, dass die Zeile von Sitzung 1 geändert wurde.
Verlorene Updates (und der Nebeneffekt von Konflikten) sollten niemals auftreten, sie sind zu 100% vermeidbar. Sie sollten das Sperren verwenden, um dies mit zwei Hauptmethoden zu verhindern: dem optimistischen und dem pessimistischen Sperren .
1) Pessimistisches Sperren
Sie möchten eine Zeile aktualisieren. In diesem Modus verhindern Sie, dass andere Benutzer diese Zeile ändern, indem Sie eine Sperre für diese Zeile anfordern ( SELECT ... FOR UPDATE NOWAIT
Anweisung). Wenn die Zeile bereits geändert wird, wird eine Fehlermeldung angezeigt, die Sie ordnungsgemäß an den Endbenutzer übertragen können (diese Zeile wird von einem anderen Benutzer geändert). Wenn die Zeile verfügbar ist, nehmen Sie Ihre Änderungen vor (UPDATE) und bestätigen Sie, wann immer Ihre Transaktion abgeschlossen ist.
2) Optimistisches Sperren
Sie möchten eine Zeile aktualisieren. Sie möchten jedoch keine Sperre für diese Zeile aufrechterhalten, weil Sie möglicherweise mehrere Transaktionen zum Aktualisieren der Zeile verwenden (webbasierte Anwendung ohne Status) oder weil Sie nicht möchten, dass ein Benutzer eine Sperre zu lange hält ( Dies kann dazu führen, dass andere Personen blockiert werden. In diesem Fall werden Sie nicht sofort eine Sperre anfordern. Sie werden einen Marker verwenden, um sicherzustellen, dass sich die Zeile nicht geändert hat, wenn Ihr Update veröffentlicht wird. Sie können den Wert aller Spalten zwischenspeichern oder eine Zeitstempelspalte, die automatisch aktualisiert wird, oder eine sequenzbasierte Spalte verwenden. Unabhängig von Ihrer Wahl stellen Sie bei der Durchführung des Updates sicher, dass sich die Markierung in dieser Zeile nicht geändert hat, indem Sie eine Abfrage wie die folgende absenden:
SELECT <...>
FROM table
WHERE id = :id
AND marker = :marker
FOR UPDATE NOWAIT
Wenn die Abfrage eine Zeile zurückgibt, führen Sie Ihre Aktualisierung durch. Wenn dies nicht der Fall ist, bedeutet dies, dass die Zeile seit Ihrer letzten Abfrage geändert wurde. Sie müssen den Vorgang von Anfang an neu starten.
Hinweis: Wenn Sie allen Anwendungen, die auf Ihre Datenbank zugreifen, vollkommen vertrauen, können Sie sich auf ein direktes Update für das optimistische Sperren verlassen. Sie könnten direkt ausstellen:
UPDATE table
SET <...>,
marker = marker + 1
WHERE id = :id;
Wenn die Anweisung keine Zeile aktualisiert, wissen Sie, dass jemand diese Zeile geändert hat und Sie müssen von vorne beginnen.
Wenn alle Anwendungen diesem Schema zustimmen, werden Sie niemals von einer anderen Person blockiert und vermeiden das blinde Update. Wenn Sie die Zeile jedoch nicht im Voraus sperren, besteht weiterhin die Gefahr einer unbefristeten Sperre, wenn eine andere Anwendung, ein Stapeljob oder eine direkte Aktualisierung keine optimistische Sperre implementiert. Aus diesem Grund empfehle ich, die Zeile unabhängig von der Auswahl des Sperrschemas immer zu sperren (der Leistungseinbruch kann vernachlässigbar sein, da Sie beim Sperren der Zeile alle Werte einschließlich der Zeilen-ID abrufen).
TL; DR
- Das Aktualisieren einer Zeile ohne vorherige Sperre setzt die Anwendung einem möglichen "Einfrieren" aus. Dies kann vermieden werden, wenn alle DMLs in der Datenbank optimistische oder pessimistische Sperren implementieren.
- Stellen Sie sicher, dass die SELECT-Anweisung Werte zurückgibt, die mit früheren SELECT-Anweisungen übereinstimmen (um ein verlorenes Aktualisierungsproblem zu vermeiden).