Datenbanktransaktionen, ACID, CAP-Theorem und BASE – IT-Berufe-Podcast #187

Um Datenbanktransaktionen, die ACID-Prinzipien und Alternativen dazu geht es in der einhundertsiebenundachzigsten Episode des IT-Berufe-Podcasts.

Probeabo bei Audible (Affiliate)

Inhalt

Datenbanktransaktionen sollten jedem/jeder ITler:in etwas sagen, da wir fast täglich mit datenbankgestützten Anwendungen arbeiten, egal, ob wir selbst diese Anwendungen programmieren oder „nur“ Abfragen gegen eine Datenbank durchführen.

Was ist eine Datenbanktransaktion?

Eine Transaktion ist eine Menge aus mehreren zusammenhängenden Datenbankoperationen, die gemeinsam als eine Einheit durchgeführt werden müssen.

Beispiele für Datenbanktransaktionen:

  • Banküberweisung von 100 EUR von Konto DE123 auf Konto DE432
    • UPDATE konto SET kontostand = kontostand - 100 WHERE iban = 'DE123';
    • UPDATE konto SET kontostand = kontostand + 100 WHERE iban = 'DE432';
  • Neuen Tag katze zu einem Blog-Post mit ID 123 hinzufügen
    • INSERT INTO tag (id, name) VALUES (1, 'katze');
    • INSERT INTO tag_post (post_id, tag_id) VALUES (123, 1);
  • Neue Bestellung für einen Kunden mit ID 324 erfassen für Artikel 253
    • INSERT INTO bestellung (id, datum, kunde_id) VALUES (123, '2024-04-10', 324);
    • INSERT INTO bestellposition (bestellung_id, artikel_id, menge, preis) VALUES (123, 253, 1, 123.92);
  • Neuen Tarifsatz einer Versicherung anlegen und bisherigen beenden
    • UPDATE tarif SET gueltig_bis='2024-04-10' WHERE id=122;
    • INSERT INTO tarif (id, gueltig_ab, beitrag) VALUES (123, '2024-04-10', 143.23);

Begriffsabgrenzung

Eine Datenbanktransaktion ist nicht zu verwechseln mit einer Transaktion im Geschäftsbetrieb, z.B. einer Überweisung bei einer Bank, dem Kauf eines Autos oder der Buchung eines Fluges.

Die ACID-Prinzipien

Datenbanktransaktion müssen/sollen bestimmten Kriterien genügen, die als ACID-Prinzipien bekannt sind.

  • Atomarität/Atomicity: Alle Datenbankoperationen werden entweder vollständig gemeinsam durchgeführt oder gar nicht. Es kann nicht sein, dass nur einige Operationen durchgeführt werden und andere nicht. Dazu werden die Datenbankoperationen in eine Transaktion „eingeklammert“.
    • Beispiel: Bei der Banküberweisung darf nicht nur Geld abgebucht oder gutgeschrieben werden, sondern beide Buchungen müssen gemeinsam durchgeführt werden.
  • Konsistenz/Consistency: Wenn die Datenbank vor der Transaktion in einem konsistenten Zustand war, dann muss sie es auch nach der Transaktion sein.
    • Beispiel: Bei der Banküberweisung bleibt der Gesamtbetrag an Geld gleich. Es entsteht kein Geld aus dem Nichts und es geht auch kein Geld verloren.
  • Isolation/Isolation: Mehrere Transaktionen dürfen sich nicht gegenseitig beeinflussen. Hierzu folgen weiter unten verschiedene Maßnahmen zur Umsetzung.
    • Beispiel: Bei zwei parallelen Banküberweisungen vom gleichen Konto müssen beide Beträge nacheinander abgebucht werden und nicht nur der der zuletzt durchgeführten Transaktion.
  • Dauerhaftigkeit/Durability: Die Daten müssen nach Abschluss der Transaktion persistent gespeichert sein und z.B. auch einen Systemausfall überstehen. Das wird durch sogenannte Transaktionslogs sichergestellt.
    • Beispiel: Wenn nach dem Abschluss einer Transaktion der Datenbankprozess abstürzt, müssen auch nach dem Neustart der Datenbank die aktualisierten Daten vorhanden sein.

Maßnahmen zur Wahrung der Isolation von Transaktionen

Wenn Transaktionen nicht isoliert voneinander ablaufen, können verschiedene Probleme in der Datenbank auftreten.

  • Dirty Read: Veränderte Daten einer noch offenen Transaktion werden von einer anderen Transaktion gelesen und weisen somit einen „dreckigen“ Zustand auf, weil er noch nicht final ist.
    • Beispiel: Bei einem Ticketkauf geht die zweite Buchung schon vom veränderten Bestand der ersten Transaktion aus.
  • Lost Updates: Wenn zwei Transaktionen gleichzeitig denselben Datensatz verändern, „gewinnt“ die Änderung der zuletzt durchgeführten Transaktion und die der ersten ist verloren.
    • Beispiel: Bei zwei Banküberweisungen wird der Kontostand auf den Ursprungsstand abzgl. der zweiten Überweisung gesetzt, aber ohne die erste Überweisung abzuziehen.
  • Non-Repeatable Read: Wiederholte Lesevorgänge innerhalb einer Transaktion liefern unterschiedliche Ergebnisse.
    • Beispiel: Bei einer Flugbuchung wird geprüft, ob noch ausreichend Plätze frei sind, aber durch eine parallele Buchung wird bei der zweiten Abfrage ein unterschiedliches Ergebnis geliefert.
  • Phantom Read: Während eine Transaktion Datensätze nach einem bestimmten Kriterium liest, werden weitere Datensätze zum gleichen Kriterium hinzugefügt/gelöscht/verändert.
    • Beispiel: Während der Durchschnittsberechnung von Gehältern von Mitarbeiter:innen wird ein neuer Mitarbeiter hinzugefügt, der beim Summieren des Gehalts noch nicht berücksichtigt wird, aber beim Zählen der Datensätze dann schon.

Die Datenbank kann verschiedene Transaktionsisolationsebenen implementieren, um den obigen Problemen entgegenzuwirken. Grundsätzlich werden dabei Sperren verwendet, um die gleichzeitige Verarbeitung der Daten einzuschränken. Es kann dabei eine Lesesperre und/oder eine Schreibsperre gesetzt werden, wodurch das gleichzeitige Lesen bzw. Schreiben eingeschränkt wird.

  • Read Uncommitted: Es werden keine Sperren verwenden. Quasi kein Schutz vor obigen Problemen. Vergleichbar mit NO ACTION bei referenzieller Integrität.
  • Read Committed: Für die gesamte Transaktion werden Schreibsperren auf alle beteiligten Objekte gesetzt, die verändert werden sollen. Lesesperren werden aber nur kurzzeitig gesetzt. Es können Non-Repeatable Read und Phantom Read auftreten.
  • Repeatable Read: Es werden für die gesamte Transaktion Lese- und Schreibsperren auf alle beteiligten Objekte gesetzt. Nur Phantom Reads können noch auftreten (weil dabei zwei lesende Operationen mit unterschiedlichen Kriterien durchgeführt werden).
  • Serializable: Die Datenbank verhält sich so, als würden die Transaktionen komplett separiert nacheinander („seriell“) durchgeführt. Dabei kann keines der obigen Probleme mehr auftreten, aber Transaktionen müssen ggfs. abgebrochen werden. Starten z.B. zwei Flugbuchungen für zwei Sitzplätze parallel auf dem Sitzplatzbestand von 2, wird die erste Transaktion den Bestand auf 0 reduzieren und die zweite Transaktion (die „gewartet“ hat) muss abbrechen, weil die Sitzplätze nun nicht mehr ausreichen.

Umsetzung in SQL

Mit der Transaction Control Language (TCL) gibt es eine eigene Familie an SQL-Befehlen, die sich nur um die Transaktionssteuerung kümmert. Hiermit können Datenbankoperationen zu einer Transaktion zusammengefasst werden.

  • BEGIN TRANSACTION: Startet eine Datenbanktransaktion.
  • COMMIT: Beendet eine Datenbanktransaktion und schreibt die Änderungen fest.
  • ROLLBACK: Rollt eine Datenbanktransaktion zurück und verwirft alle Änderungen.

In vielen Datenbanken muss man nicht explizit Transaktionen starten und beenden. Sie verwenden ein sogenanntes Auto-Commit. Dabei wird jedes Statement in einer eigenen Transaktion durchgeführt.

Umsetzung in ORMs

Brauche ich als Softwareentwickler:in überhaupt Wissen über Datenbanktransaktionen? In modernen Entwicklungsprojekten werden doch sowieso objektrelationale Mapper (ORM) verwendet. Doch auch diese ORMs können natürlich nicht zaubern, sondern verwenden unter der Haube die normalen Datenbankoperationen. Daher ist auch bei Verwendung eines ORMs wichtig zu wissen, welche Operationen in einer gemeinsamen Transaktion durchgeführt werden müssen.

Dafür bieten viele ORMs entsprechende Befehle an. Jakarta EE hat sogar einen eigenen Standard dafür: Jakarta Transactions (JTA). Dort wird z.B. Annotation @Transactional verwendet. Diese wird an eine Java-Methode geschrieben, die dann automatisch innerhalb einer Datenbanktransaktion ausgeführt wird. Im Fall einer aufgetretenen Exception wird dabei dann automatisch ein Rollback durchgeführt.

CAP und BASE

Transaktionen nach den ACID-Prinzipien sind ein wichtiger Bestandteil vieler Datenbanksysteme. Sobald die Datenbank jedoch auf mehrere Knoten verteilt wird, können Transaktionen zu einem Problem werden. Das sogenannte CAP-Theorem besagt, dass es in einem verteilten System nicht möglich ist, alle drei Eigenschaften Konsistenz, Verfügbarkeit und Ausfalltoleranz gleichzeitig zu garantieren.

  • Konsistenz (Consistency): Die Konsistenz meint in diesem Kontext den einheitlichen Datenstand über alle Datenbankknoten hinweg (und nicht die Konsistenz vor/nach einer Transaktion). Egal, welcher Knoten abgefragt wird, es werden immer die gleichen Daten zurückgegeben.
  • Verfügbarkeit (Availability): Die Datenbank antwortet in akzeptabler Zeit auf alle Anfragen. Dabei kann es jedoch sein, dass nicht immer die aktuellsten Daten geliefert werden.
  • Partitionstoleranz (Partition tolerance): Die Datenbank arbeitet auch weiter, wenn einzelne Knoten ausfallen.

Angenommen, die verteilte Datenbank soll zu jedem Zeitpunkt in einem konsistenten Zustand sein (also alle Informationen auf allen Knoten sollen identisch sein), dann führt ein Ausfall eines Knoten automatisch dazu, dass eine Transaktion fehlschlagen muss, da der ausgefallene Knoten nicht sofort aktualisiert werden kann. Die Konsistenz wäre dann zwar gewährleistet, aber die Partitionstoleranz nicht.

Da heutzutage viele Anwendungen tatsächlich mit verteilten Datenbanken arbeiten, wurde mit BASE ein Gegenentwurf zu ACID geschaffen. Das Wort ist etwas konstruiert, um das Gegenstück zu ACID zu bilden. Aus der Chemie kennen wir den Unterschied zwischen Säure („acid“) und Lauge („base“). Im Kern stellt ACID die Konsistenz eines Systems sicher, während BASE die Verfügbarkeit in den Vordergrund stellt.

  • Basically Available: Die Datenbank ist grundsätzlich für alle Benutzer jederzeit erreichbar (hohe Verfügbarkeit).
  • Soft state: Daten können sich im Laufe der Zeit verändern und temporäre („weiche“) Zustände annehmen, wenn mehrere Änderungen parallel durchgeführt werden.
  • Eventual consistency: Irgendwann einmal werden die Daten in der Datenbank einen konsistenten Zustand erreichen. Bis dahin können Anfragen unterschiedliche Ergebnisse produzieren.
    • Vorsicht „false friend“: „eventually“ heißt auf Deutsch „schlussendlich“ und nicht „eventuell“. Es wird also immer Konsistenz erreicht, nur eben nicht unmittelbar sofort.

Literaturempfehlungen

Sascha Kersken - IT-Handbuch für Fachinformatiker: Für Fachinformatiker der Bereiche Anwendungsentwicklung und Systemintegration. Inkl. Prüfungsfragen und Praxisübungen (Affiliate)*

In Kapitel 13 gibt es eine gute und kurze Einführung in das Thema Datenbanken inkl. Transaktionen. Ich habe das Kapitel auch schon im Buchclub besprochen: Buchclub: Handbuch für Fachinformatiker (Teil 11: Datenbanken)

Links

Polyglot Clean Code Developer
About the Author
Ausbildungsleiter für Fachinformatiker Anwendungsentwicklung und Systemintegration, IHK-Prüfer und Hochschuldozent für Programmierung und Software-Engineering.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax