ACID
In der Informatik bezeichnet ACID (Atomarität, Konsistenz, Isolation, Dauerhaftigkeit) eine Reihe von Eigenschaften von Datenbanktransaktionen, die die Gültigkeit der Daten trotz Fehlern, Stromausfällen und anderen Missgeschicken garantieren sollen. Im Zusammenhang mit Datenbanken wird eine Folge von Datenbankoperationen, die die ACID-Eigenschaften erfüllen (die als eine einzige logische Operation an den Daten aufgefasst werden kann), als Transaktion bezeichnet. So ist beispielsweise eine Überweisung von einem Bankkonto auf ein anderes, auch wenn sie mehrere Änderungen wie die Belastung eines Kontos und die Gutschrift auf einem anderen beinhaltet, eine einzige Transaktion. ⓘ
1983 prägten Andreas Reuter und Theo Härder das Akronym ACID und knüpften damit an frühere Arbeiten von Jim Gray an, der bei der Charakterisierung des Transaktionskonzepts Atomarität, Konsistenz und Dauerhaftigkeit, nicht aber Isolation, nannte. Diese vier Eigenschaften sind die wichtigsten Garantien des Transaktionsparadigmas, das viele Aspekte der Entwicklung von Datenbanksystemen beeinflusst hat. ⓘ
Nach Gray und Reuter unterstützte das IBM Information Management System bereits 1973 ACID-Transaktionen (obwohl das Akronym erst später geschaffen wurde). ⓘ
Eigenschaften
Die Merkmale dieser vier Eigenschaften werden von Reuter und Härder wie folgt definiert: ⓘ
Atomarität
Transaktionen setzen sich oft aus mehreren Anweisungen zusammen. Atomarität garantiert, dass jede Transaktion als eine einzige "Einheit" behandelt wird, die entweder vollständig erfolgreich ist oder vollständig fehlschlägt: Wenn eine der Anweisungen, aus denen eine Transaktion besteht, fehlschlägt, schlägt die gesamte Transaktion fehl und die Datenbank bleibt unverändert. Ein atomares System muss in jeder Situation Atomarität garantieren, auch bei Stromausfällen, Fehlern und Abstürzen. Die Garantie der Atomarität verhindert, dass die Datenbank nur teilweise aktualisiert wird, was zu größeren Problemen führen kann, als die gesamte Serie zu verwerfen. Infolgedessen kann die Transaktion nicht von einem anderen Datenbank-Client als im Gange beobachtet werden. Zu einem bestimmten Zeitpunkt ist sie noch nicht erfolgt, und zum nächsten Zeitpunkt ist sie bereits vollständig erfolgt (oder es ist nichts geschehen, wenn die Transaktion während des Ablaufs abgebrochen wurde). ⓘ
Ein Beispiel für eine atomare Transaktion ist eine Geldüberweisung von Konto A auf Konto B. Sie besteht aus zwei Vorgängen, nämlich der Abhebung des Geldes von Konto A und der Überweisung auf Konto B. Die Durchführung dieser Vorgänge in einer atomaren Transaktion gewährleistet, dass die Datenbank in einem konsistenten Zustand bleibt, d. h., das Geld wird weder abgebucht noch gutgeschrieben, wenn einer dieser beiden Vorgänge fehlschlägt. ⓘ
Konsistenz (Korrektheit)
Die Konsistenz stellt sicher, dass eine Transaktion die Datenbank nur von einem gültigen Zustand in einen anderen bringen kann, wobei die Datenbankinvarianten beibehalten werden: Alle Daten, die in die Datenbank geschrieben werden, müssen gemäß allen definierten Regeln gültig sein, einschließlich Constraints, Kaskaden, Triggern und jeder Kombination davon. Dies verhindert eine Beschädigung der Datenbank durch eine illegale Transaktion, garantiert aber nicht, dass eine Transaktion korrekt ist. Referentielle Integrität garantiert die Beziehung zwischen Primärschlüssel und Fremdschlüssel. ⓘ
Isolierung
Transaktionen werden oft gleichzeitig ausgeführt (z. B. mehrere Transaktionen, die gleichzeitig in eine Tabelle lesen und schreiben). Durch die Isolierung wird sichergestellt, dass die Datenbank bei der gleichzeitigen Ausführung von Transaktionen in demselben Zustand verbleibt, der bei einer sequentiellen Ausführung der Transaktionen erreicht worden wäre. Die Isolierung ist das Hauptziel der Gleichzeitigkeitskontrolle; je nach der verwendeten Methode sind die Auswirkungen einer unvollständigen Transaktion für andere Transaktionen möglicherweise nicht einmal sichtbar. ⓘ
Dauerhaftigkeit
Der Begriff Dauerhaftigkeit sagt aus, dass Daten nach dem erfolgreichen Abschluss einer Transaktion garantiert dauerhaft in der Datenbank gespeichert sind. Die dauerhafte Speicherung der Daten muss auch nach einem Systemfehler (Software-Fehler oder Hardware-Ausfall) garantiert sein. Insbesondere darf es nach einem Ausfall des Hauptspeichers nicht zu Datenverlusten kommen. Dauerhaftigkeit kann durch das Schreiben eines Transaktionslogs sichergestellt werden. Ein Transaktionslog erlaubt es, nach einem Systemausfall alle noch fehlenden Schreib-Operationen in der Datenbank auszuführen. ⓘ
Die Dauerhaftigkeit garantiert, dass eine einmal festgeschriebene Transaktion auch im Falle eines Systemausfalls (z. B. Stromausfall oder Absturz) festgeschrieben bleibt. Dies bedeutet in der Regel, dass abgeschlossene Transaktionen (oder ihre Auswirkungen) in einem nichtflüchtigen Speicher aufgezeichnet werden. ⓘ
Konsistenzerhaltung
Konsistenz heißt, dass eine Transaktion nach Beendigung einen konsistenten Datenbankzustand hinterlässt, falls die Datenbank davor auch konsistent war. Dies beinhaltet, dass alle im Datenbankschema definierten Integritätsbedingungen vor dem Abschluss der Transaktion überprüft werden. Ist das nicht möglich, oder tritt ein Fehler auf, wird die gesamte Transaktion rückgängig gemacht. ⓘ
Isolation (Abgrenzung)
Durch das Prinzip der Isolation wird verhindert/eingeschränkt, dass sich nebenläufig in Ausführung befindliche Transaktionen gegenseitig beeinflussen. Realisiert wird dies üblicherweise durch Sperrverfahren, die vor einem Datenzugriff die benötigten Daten für andere Transaktionen sperren. Sperrverfahren schränken die Nebenläufigkeit ein und können zu Blockierungen führen. In vielen Datenbanksystemen kann das verwendete Isolationsverfahren daher so konfiguriert werden, dass bestimmte eigentlich unerwünschte Effekte zugelassen werden um eine höhere Nebenläufigkeit zu erreichen. Der transaktionale Isolationsgrad definiert dabei die erlaubte Art der Beeinflussung, verbreitete Einstellungen sind dabei READ COMMITTED
, REPEATABLE READ
sowie SERIALIZABLE
. ⓘ
Beispiele
Die folgenden Beispiele verdeutlichen die ACID-Eigenschaften. In diesen Beispielen hat die Datenbanktabelle zwei Spalten, A und B. Eine Integritätsbeschränkung verlangt, dass der Wert in A und der Wert in B die Summe 100 ergeben müssen. Der folgende SQL-Code erstellt eine Tabelle wie oben beschrieben:
Atomarität
Atomarität ist die Garantie, dass eine Reihe von Datenbankoperationen in einer atomaren Transaktion entweder alle auftreten (eine erfolgreiche Operation) oder keine (eine erfolglose Operation). Die Reihe von Operationen kann nicht getrennt werden, wenn nur einige von ihnen ausgeführt werden, was die Reihe von Operationen "unteilbar" macht. Durch die Gewährleistung der Atomarität wird verhindert, dass die Datenbank nur teilweise aktualisiert wird, was zu größeren Problemen führen kann, als wenn die gesamte Serie abgelehnt wird. Mit anderen Worten: Atomarität bedeutet Unteilbarkeit und Irreduzierbarkeit. Alternativ kann man auch sagen, dass eine logische Transaktion aus mehreren physischen Transaktionen besteht. Solange nicht alle physischen Transaktionen ausgeführt wurden, ist die logische Transaktion nicht erfolgt. Wenn die logische Transaktion darin besteht, Geld von Konto A auf Konto B zu überweisen, kann dies aus folgenden Schritten bestehen: Zuerst wird der Betrag von Konto A abgehoben, dann wird derselbe Betrag auf Konto B eingezahlt. Wir möchten nicht sehen, dass der Betrag von Konto A abgehoben wird, bevor wir sicher sind, dass er auch auf Konto B überwiesen wurde. ⓘ
Fehlerhafte Konsistenz
Konsistenz ist ein sehr allgemeiner Begriff, der besagt, dass die Daten alle Validierungsregeln erfüllen müssen. Im vorherigen Beispiel ist die Validierung eine Anforderung, dass A + B = 100. Um die Konsistenz zu gewährleisten, müssen alle Validierungsregeln überprüft werden. Angenommen, eine Transaktion versucht, 10 von A ohne Änderung von B. Da die Konsistenz nach jeder Transaktion geprüft wird, ist bekannt, dass A + B = 100 ist, bevor die Transaktion beginnt. Entfernt die Transaktion 10 von A erfolgreich entfernt, wird Atomarität erreicht. Eine Validierungsprüfung wird jedoch zeigen, dass A + B = 90, was mit den Regeln der Datenbank unvereinbar ist. Die gesamte Transaktion muss abgebrochen und die betroffenen Zeilen in den Zustand vor der Transaktion zurückversetzt werden. Hätte es andere Constraints, Trigger oder Kaskaden gegeben, wäre jede einzelne Änderungsoperation auf die gleiche Weise wie oben geprüft worden, bevor die Transaktion bestätigt wurde. Ähnliche Probleme können auch bei anderen Beschränkungen auftreten. Wir könnten die Datentypen von sowohl A und B ganze Zahlen sein müssen. Wenn wir dann z. B. den Wert 13,5 für Aeingeben, wird die Transaktion abgebrochen, oder das System kann eine Warnung in Form eines Triggers auslösen (falls der Trigger zu diesem Zweck geschrieben wurde). Ein weiteres Beispiel wären Integritätsbeschränkungen, die es nicht erlauben, eine Zeile in einer Tabelle zu löschen, auf die sich mindestens ein Fremdschlüssel in anderen Tabellen bezieht. ⓘ
Versagen der Isolierung
Um die Isolation zu demonstrieren, nehmen wir an, dass zwei Transaktionen gleichzeitig ausgeführt werden, die beide versuchen, dieselben Daten zu ändern. Eine der beiden Transaktionen muss warten, bis die andere abgeschlossen ist, um die Isolation aufrechtzuerhalten. ⓘ
Betrachten wir zwei Transaktionen:
- T1 überträgt 10 von A nach B.
- T2 überträgt 20 von B nach A. ⓘ
Zusammengenommen gibt es vier Aktionen:
- T1 subtrahiert 10 von A.
- T1 addiert 10 zu B.
- T2 subtrahiert 20 von B.
- T2 addiert 20 zu A. ⓘ
Wenn diese Vorgänge in der richtigen Reihenfolge ausgeführt werden, bleibt die Isolation erhalten, obwohl T2 warten muss. Was passiert, wenn T1 auf halbem Weg ausfällt? Die Datenbank eliminiert die Auswirkungen von T1, und T2 sieht nur gültige Daten. ⓘ
Durch die Verschachtelung der Transaktionen kann sich die tatsächliche Reihenfolge der Aktionen ändern:
- T1 subtrahiert 10 von A.
- T2 subtrahiert 20 von B.
- T2 addiert 20 zu A.
- T1 addiert 10 zu B. ⓘ
Was passiert, wenn T1 während der Änderung von B in Schritt 4 ausfällt? Zu dem Zeitpunkt, an dem T1 ausfällt, hat T2 bereits A geändert; es kann nicht auf den Wert zurückgesetzt werden, den es vor T1 hatte, ohne eine ungültige Datenbank zu hinterlassen. Dies wird als Schreib-Schreib-Konflikt bezeichnet, da zwei Transaktionen versucht haben, in dasselbe Datenfeld zu schreiben. In einem typischen System würde das Problem dadurch gelöst werden, dass man zum letzten bekannten guten Zustand zurückkehrt, die fehlgeschlagene Transaktion T1 abbricht und die unterbrochene Transaktion T2 vom guten Zustand aus neu startet. ⓘ
Dauerhafter Ausfall
Betrachten wir eine Transaktion, die 10 von A nach B überträgt. Zuerst werden 10 von A entfernt, dann werden 10 zu B hinzugefügt. Die Änderungen befinden sich jedoch noch in der Warteschlange des Plattenpuffers und warten darauf, auf die Festplatte übertragen zu werden. Fällt die Stromversorgung aus, gehen die Änderungen verloren, aber der Benutzer geht (verständlicherweise) davon aus, dass die Änderungen bestehen bleiben. ⓘ
Implementierung
Die Verarbeitung einer Transaktion erfordert oft eine Abfolge von Operationen, die aus verschiedenen Gründen fehlschlagen kann. So kann es beispielsweise sein, dass das System keinen Platz mehr auf den Festplatten hat oder die zugewiesene CPU-Zeit aufgebraucht ist. Es gibt zwei gängige Techniken: Write-Ahead-Logging und Shadow-Paging. In beiden Fällen müssen Sperren für alle zu aktualisierenden Informationen und, je nach Isolationsgrad, möglicherweise auch für alle Daten, die gelesen werden können, erworben werden. Beim Write-Ahead-Logging wird die Dauerhaftigkeit dadurch gewährleistet, dass die ursprünglichen (unveränderten) Daten in ein Protokoll kopiert werden, bevor die Datenbank geändert wird. Dadurch kann die Datenbank im Falle eines Absturzes in einen konsistenten Zustand zurückkehren. Beim Shadowing werden Aktualisierungen auf eine Teilkopie der Datenbank angewendet, und die neue Kopie wird aktiviert, wenn die Transaktion bestätigt wird. ⓘ
Sperren vs. Multiversioning
Viele Datenbanken verlassen sich auf Sperren, um ACID-Fähigkeiten zu bieten. Sperren bedeutet, dass die Transaktion die Daten, auf die sie zugreift, markiert, so dass das DBMS weiß, dass andere Transaktionen sie nicht ändern dürfen, bis die erste Transaktion erfolgreich ist oder fehlschlägt. Die Sperre muss immer erworben werden, bevor Daten verarbeitet werden, auch solche, die gelesen, aber nicht verändert wurden. Nicht-triviale Transaktionen erfordern in der Regel eine große Anzahl von Sperren, was zu erheblichem Overhead und zur Blockierung anderer Transaktionen führt. Wenn beispielsweise Benutzer A eine Transaktion ausführt, die eine Datenzeile lesen muss, die Benutzer B ändern möchte, muss Benutzer B warten, bis die Transaktion von Benutzer A abgeschlossen ist. Um eine vollständige Isolation zu gewährleisten, wird häufig eine zweistufige Sperrung verwendet. ⓘ
Eine Alternative zum Sperren ist die Multiversions-Gleichzeitigkeitskontrolle, bei der die Datenbank jeder lesenden Transaktion die vorherige, unveränderte Version von Daten zur Verfügung stellt, die gerade von einer anderen aktiven Transaktion geändert werden. Dies ermöglicht es den Lesern, ohne Sperren zu arbeiten, d. h. schreibende Transaktionen blockieren keine lesenden Transaktionen und Leser blockieren keine schreibenden Transaktionen. Um auf das Beispiel zurückzukommen: Wenn die Transaktion von Benutzer A Daten anfordert, die von Benutzer B geändert werden, stellt die Datenbank A die Version dieser Daten zur Verfügung, die existierte, als Benutzer B seine Transaktion startete. Benutzer A erhält eine konsistente Sicht auf die Datenbank, auch wenn andere Benutzer Daten ändern. Eine Implementierung, nämlich die Snapshot-Isolation, lockert die Isolationseigenschaft auf. ⓘ
Verteilte Transaktionen
Die Gewährleistung von ACID-Eigenschaften in einer verteilten Transaktion in einer verteilten Datenbank, in der kein einzelner Knoten für alle Daten, die eine Transaktion betreffen, verantwortlich ist, bringt zusätzliche Komplikationen mit sich. Netzwerkverbindungen können ausfallen, oder ein Knoten kann seinen Teil der Transaktion erfolgreich abschließen und muss dann seine Änderungen aufgrund eines Fehlers auf einem anderen Knoten zurücknehmen. Das Zwei-Phasen-Commit-Protokoll (nicht zu verwechseln mit dem Zwei-Phasen-Locking) sorgt für Atomarität bei verteilten Transaktionen, um sicherzustellen, dass jeder Teilnehmer an der Transaktion zustimmt, ob die Transaktion übertragen werden soll oder nicht. Kurz gesagt: In der ersten Phase fragt ein Knoten (der Koordinator) die anderen Knoten (die Teilnehmer) ab, und erst wenn alle antworten, dass sie bereit sind, formalisiert der Koordinator in der zweiten Phase die Transaktion. ⓘ
Probleme und Einschränkungen
In verteilten Datenbanken kommt es zu Problemen, wenn alle ACID-Eigenschaften erfüllt werden sollen und gleichzeitig eine hohe Verfügbarkeit erreicht werden soll. Diese Probleme wurden in dem CAP-Theorem von Brewer formuliert. Im Umfeld der NoSQL-Datenbanken wird daher häufig das BASE-Prinzip (Basically Available, Soft state, Eventual consistency) verfolgt. ⓘ