- Verlorene Updates
-
Verlorenes Update (auch engl. Lost Update) bezeichnet in der Informatik einen Fehler, der bei mehreren parallelen Schreibzugriffen auf eine gemeinsam genutzte Information auftreten kann. Wenn zwei Transaktionen dieselbe Information verändern, dann können die Änderungen der ersten sofort durch die Änderungen der zweiten überschrieben werden.
Dabei spielt es keine Rolle, ob die „gemeinsam genutzte Information“ in einer Datei, in einer Datenbanktabelle oder im Arbeitsspeicher steht.
Inhaltsverzeichnis
A: Lesen und Schreiben ohne Interaktion mit einem Benutzer
Beispiel
Eine Kette von Vorverkaufsstellen speichert für jede Veranstaltung die Anzahl der verkauften Karten. Es wurden bereits 100 Karten verkauft, als an einer Kasse fünf Karten zurückgegeben werden. Zur gleichen Zeit werden an einer zweiten Kasse drei Karten gekauft. Das System der ersten Kasse zieht die 5 zurückgegebenen Karten von den 100 ab und schreibt den neuen Wert – 95 – wieder in die Datenbank. Das zweite Kassensystem addiert die drei soeben verkauften Karten zu der 100 dazu und schreibt diesen Wert (103) ebenfalls in die Datenbank. Der zuerst geschriebene Wert geht dabei verloren, das Endergebnis ist falsch (103 verkaufte Karten, obwohl es tatsächlich nur 98 sind).
Zeitpunkt Programm 1 5 Karten zurücknehmen
Gespeicherte Anzahl verkaufter Karten
Programm 2 3 Karten verkaufen
0 100 1 Anzahl der verkauften Karten lesen Ergebnis: 100
100 2 100 Anzahl der verkauften Karten lesen Ergebnis: 100
3 5 Karten werden zurückgenommen Neuen Wert berechnen: 100-5=95
Neuen Wert (95) schreiben
95 4 103 3 Karten werden verkauft Neuen Wert berechnen: 100+3=103
Neuen Wert (103) schreiben
Welche Sperrmechanismen stehen zur Verfügung?
Bei der Ausführung des Lesezugriffs wird die gemeinsam genutzte Information gesperrt, damit ein zwischenzeitliches Abändern durch ein anderes Programm nicht möglich ist.
Die dafür benötigten Sperrmechanismen werden von den verschiedenen Datenverwaltungssystemen bereitgestellt:
- der Share-Lock ermöglicht beliebig vielen Transaktionen einen Lesezugriff.
- der Exclusiv-Lock ermöglicht nur einer einzigen Transaktion einen schreibenden Zugriff.
Diese Sperrmechanismen werden sowohl von den meisten Betriebssystemen und Datenbanken als auch von Buffer-Managern verwendet, um konkurrierende Zugriffe zu handhaben.
Der Isolationlevel RR
Oft wird der Isolationlevel RR als die Lösung des Lost-Update-Problems genannt.
Die meisten RDBMS bieten verschiedene Isolationlevel an. RR bedeutet, dass ein Share-Lock bis zum Ende einer Transaktion bestehen bleibt und nicht (wie z. B. bei Isolationlevel CS) direkt nach dem Lesezugriff wieder verschwindet.
Wenn man den Isolationlevel RR verwendet, dann muss man darauf achten, dass keine Deadlocks entstehen.
Das erste Programm schreibt einen Share-Lock, der nicht wieder entfernt wird. Das zweite Programm schreibt auch einen Share-Lock. Nun will das erste Programm den Share-Lock in einen Exclusiv-Lock umwandeln, doch das geht nicht, solange das zweite Programm noch seinen Share-Lock aufrechterhält. Etwas später will das zweite Programm ebenfalls seinen Share-Lock in einen Exclusiv-Lock umwandeln. Nun wartet jeder auf den anderen. Das ist die klassische Deadlock-Situation.
Zeitpunkt Programm 1 5 Karten zurücknehmen
Locks von Prog. 1
Gespeicherte Anzahl verkaufter Karten
Locks von Prog. 2
Programm 2 3 neue Karten verkaufen
0 100 1 Anzahl der verkauften Karten lesen Ergebnis: 100
S-Lock von P1
100 2 S-Lock von P1
100 S-Lock von P2
Anzahl der verkauften Karten lesen Ergebnis: 100
3 5 Karten werden zurückgenommen Neuen Wert berechnen: 100-5=95
Exclusive-Lock anfordern
warten auf P2
S-Lock von P1
100 S-Lock von P2
4 warten auf P2 S-Lock von P1
100 S-Lock von P2
3 Karten werden verkauft Neuen Wert berechnen: 100+3=103
Exclusive-Lock anfordern
warten auf P1
5 warten auf P2 S-Lock von P1
100 S-Lock von P2
warten auf P1 Nun kommt es darauf an, wie das RDBMS in so einem Fall reagiert. Einige RDBMS z. B. DB2 kann man so parametrisieren, dass eine Transaktion nur eine bestimmte Zeit auf gesperrte Ressourcen wartet. Wenn die Zeit verstrichen ist und die Ressource immer noch gesperrt ist, dann wird die Transaktion zurückgerollt und das Programm erhält eine Fehlermeldung (SQLCODE −911). Das wäre eine Lösung für das Problem, denn durch den Rollback z. B. der ersten Transaktion ist auch der Share-Lock entfernt worden und die zweite Transaktion bekommt nun den Exclusiv-Lock, auf den sie gewartet hat. Wenn im Programm der SQLCODE −911 gezielt abgefragt wird, dann kann das Programm in einem solchen Fall den Satz erneut lesen und erhält nun den Wert, den das andere Programm gerade geschrieben hat. So geht kein Update verloren.
Zeitpunkt Programm 1 5 Karten zurücknehmen
Locks von Prog. 1
Gespeicherte Anzahl verkaufter Karten
Locks von Prog. 2
Programm 2 3 neue Karten verkaufen
6 SQLCODE −911 Rollback
100 S-Lock von P2
warten auf P1 7 103 X-Lock von P2
Neuen Wert (103) schreiben 8 Anzahl der verkauften Karten lesen Share-Lock anfordern
warten auf P2
103 X-Lock von P2
9 Share-Lock anfordern warten auf P2
103 commit 10 Share-Lock erhalten Anzahl der verkauften Karten lesen
Ergebnis: 103
S-Lock von P1
103 11 5 Karten werden zurückgenommen Neuen Wert berechnen: 103-5=98
X-Lock anfordern
Neuen Wert (98) schreiben
X-Lock von P1
98 12 Commit 98 Der zweite Versuch kann genauso wie der erste Versuch misslingen, weil inzwischen ein drittes Programm einen Share-Lock auf den Satz gelegt hat. Daher muss das Lesen und Schreiben im Programm in einer Schleife ausgeführt werden.
Nun kann man sich überlegen, ob die Schleife beliebig oft wiederholt werden soll, oder ob nach n Versuchen die Verarbeitung dann doch aufgegeben und mit einer Fehlermeldung beendet werden soll.
Schleife Select ... Neuen Wert berechnen Update ... if (sqlcode not in (0, −911)) return(FEHLER) Until (sqlcode = 0 or Anz_Schleifen_Durchlaeufe > n) if (sqlcode <> 0) return(FEHLER)
Falls das RDBMS im Fall eines Deadlock so lange wartet, bis ein Administrator eingreift, dann ist diese Variante keine gute Lösung.Verarbeitung serialisieren
Wenn das erste Programm die Information schon gleich beim Lese-Zugriff exclusiv sperrt, dann muss das zweite Programm schon mit seinem Lesezugriff warten. Sobald das erste Programm auch den Schreibzugriff ausgeführt hat und die Ressource wieder freigibt, kann das zweite Programm seine Verarbeitung fortsetzen. Diese Variante ist eine erzwungene Serialisierung der Verarbeitung.
Wenn die Information in einer Datei gespeichert wird, muss das Programm die Datei gleich zum Schreiben öffnen.
Wenn die Information in einer Datenbank-Tabelle gespeichert wird, dann kann der Satz z. B. durch einen CURSOR FOR UPDATE gelesen werden oder die gesamte Tabelle kann durch LOCK TABLE IN EXCLUSIVE MODE gesperrt werden.
Lese- und Schreibzugriff atomisieren
Wenn zwischen dem Lesezugriff und dem Schreibzugriff keine weitere Verarbeitung erforderlich ist, dann kann man diese beiden Zugriffe auch zusammenfassen (Siehe Atomare Operation).
Bei einem RDBMS könnte der Zugriff für das Beispiel lauten:
update Tab set Anzahl_verkaufte_Karten = Anzahl_verkaufte_Karten + :Aktueller_Verkauf
Dadurch entfällt ein gesonderter Lese-Zugriff. Ein verlorener Update kann nicht mehr vorkommen.
B: Lesen und Schreiben mit Interaktion mit einem Benutzer
In der Praxis kommt das „verlorene Update-Problem“ auch häufig in Verbindung mit Benutzer-Interaktionen vor. Damit ist gemeint, dass die gelesenen Informationen an den Benutzer ausgegeben werden und von ihm verändert werden können. Danach werden die geänderten Informationen zurückgeschrieben. Wenn ein anderer Benutzer dieselben Informationen ändern will, dann kann es sein, dass die Änderungen des ersten Benutzers verloren gehen. Folgendes ist bei Benutzerinteraktion anders, als im Fall A:
- Lesen und Schreiben können nicht verschmolzen werden.
- Lesen und Schreiben werden in den meisten Fällen als zwei unabhängige Transaktionen ausgeführt.
- Es muss berücksichtigt werden, dass sich der Benutzer möglicherweise viel Zeit lässt mit der Eingabe (z. B. Mittagspause) Das bedeutet, dass viel Zeit zwischen Lesen und Schreiben vergehen kann.
- Während der Benutzerinteraktion kann die Verbindung abbrechen (Netzwerk-Problem, das Programm wird beendet).
Verhindern kann man das, indem man vor dem Schreiben überprüft, ob die Daten inzwischen geändert wurden und diesen Lese- und Schreibzugriff in einer einzigen Transaktion zusammenfasst. Dabei muss nicht jeder einzelne Spaltenwert des Datensatzes geprüft werden. Es genügt zu wissen, ob der Datensatz geändert wurde. Das kann durch eine zusätzliche Spalte mit einer Ganzzahl erreicht werden. Dabei wird geprüft, ob beim Schreibzugriff noch der Wert wie beim Lesezugriff vor der Datenänderung vorhanden ist und wenn das wahr ist um den Wert 1 hochgezählt wird. Gleichzeitig werden auch die anderen Spaltenwerte aktualisiert. Man muss sich also nur eine einzige Ganzzahl gelesener_spaltentestwert merken. In SQL sieht das etwa so aus:
- Der Datensatz wird gelesen um anschließend bearbeitet werden zu können. (gelesener_spaltentestwert muss in eine Variable abgespeichert werden):
SELECT tabelle spalte_1, ..., gelesener_spaltentestwert WHERE id_tabelle;
- Der Datensatz wird verändert und anschließend zurückgeschrieben. (Die Überprüfung auf Änderung durch konkurrierende Zugriffe und der Schreibvorgang werden zusammengefasst):
UPDATE tabelle SET spalte_1=spaltenwert_1, ..., gelesener_spaltentestwert=gelesener_spaltentestwert+1 WHERE spaltentestwert=gelesener_spaltentestwert AND id_tabelle;
- Wird hierbei kein Datensatz gefunden, stimmt gelesener_spaltentestwert nicht mehr und es hat zwischenzeitlich ein update durch einen anderen Zugriff stattgefunden. Es muss also jetzt eine Fehlerauswertung erfolgen und entsprechend reagiert werden.
- Im Erfolgsfall ist die Bearbeitung des Datensatzes jetzt beendet.
Die Spaltenwerte sind dann natürlich entsprechend der verwendeten Programmiersprache als Variablen anzugeben.
Siehe auch
Wikimedia Foundation.