Transaktionsisolation

English

Dieser Artikel basiert auf einem Vortrag, den ich bei der Ruby on Rails User Group Schweiz in Zürich gehalten habe.

Naiver Zugang zu Transaktionen

Alle reden von Transaktionen, aber wer kennt sich wirklich gut damit aus?
Ist es vielleicht wie beim Multithreading, wo man sehr viele Leute findet, die behaupten, sich gut auszukennen, aber sehr wenige Multithreading-Programme, die wirklich unter Last auch korrekt laufen?

Immerhin haben wir so eine ganz brauchbare Vorstellung:

  • Wir beginnen implizit oder explizit eine Transaktion
  • Wir führen eine oder mehrere Operationen durch (z.B. SQL-Befehle)
  • Wir beschließen am Ende entweder ein „roll-back“ oder „commit“
  • Roll-back stellt den Zustand von vor der Transaktion wieder her
  • Commit akzeptiert die Änderungen definitiv

Das ist doch überschaubar. Warum brauchen wir da noch komplizierten Kram?

Wenn wir uns in einer einfachen Welt bewegen, wo nur ein DB-User mit einer Session auf der Datenbank ist.

Die komplizierte Welt

In einer komplizierten Welt finden leider parallele Aktivitäten statt.
Mehrere Threads, Prozesse oder auch externe Datenbank-Clients greifen auf die Datenbank zu.

Was machst das so kompliziert?
Wenn Dinge parallel werden, dann ist jeder Entwickler gut darin, das im Griff zu haben, aber komischerweise funktioniert es dann meistens gar nicht oder zumindest nur auf dem Laptop des Entwicklers, nicht aber langfristig auf dem Server unter Last.
Aber die Leute, die Datenbankprodukte entwickeln, sind wirklich gut. Datenbanken laufen mit vielen parallelen Zugriffen unter Last auf Millionen von Rechner und funktionieren dabei noch recht zuverlässig.
Nun haben wir aber ein paar zusätzliche Fragen:

  • Was passiert mit unvollständigen Transaktionen vor dem Commit?
  • Was passiert mit erfolgreich abgeschlossenen Transaktionen während eines langen SELECTs oder einer langen lesenden Transaktion?
  • Dasselbe betrifft auch lange Transaktionen, die Daten verändern, aber dabei auch lesend zugreifen.

Eine Naive Anforderung wäre, dass andere Clients die Transaktion entweder ganz oder gar nicht sehen. Reicht das?
Man stelle sich ein Problem vor, das in einer langweiligen Informatik I-Vorlesung in meinem Studium real war:

  • Die Vorlesung geht 90 min ohne Pause.
  • Der „Hörsal“ ist eine Maschinenhalle, die man provisorisch mit Stühlen ausgestattet hat.
  • Etwa 600 Leute sind anwesend.
  • Es gehen dauernd Leute raus.
  • Am Schluss der Vorlesung ist der Hörsal immer noch ziemlich voll.
  • Wie viele Leute sind zu einer bestimmten Zeit in dem Hörsal?
  • Eine präzise und korrekte Zahl existiert immer, denn so brutale Sachen wie halbe Studenten waren damals nicht üblich.
  • Aber das Zählen ist schwierig, weil während dem Zählen dauernd Leute den Raum verlassen.
  • Merke: Das Verlassen des Hörsals ist eine Transaktion. Nach dem Commit ist die betreffende Person draußen, vorher drin.

Da kommt nun das berühmte Snapshot too old ins Spiel, das einige von uns, die noch mit Oracle arbeiten, recht gut kennen. Oracle hat das Problem, die Hörer im Hörsaal zu zählen, nämlich gelöst. Und ähnliche Probleme auch. Das SELECT bei Oracle bekommt für seine Verarbeitungszeit einen konsistenten Datenstand, der auf dem Zustand beim Start der Abfrage basiert.
Transaktionen, die währenddessen erfolgreich mit einem Commit abgeschlossen werden, werden nicht berücksichtigt. Das wird „Snapshot“ genannt. Sehr lange laufende SELECTs, die auf Daten zugreifen, die sich häufig ändern, scheitern manchmal mit „Snapshot too old“.

Präzisierung

Diese Konzepte werden jetzt etwas präzisiert, aber die Details nachzulesen sprengt sicher den Rahmen eines Blog-Artikels.
Es geht um:

  • Anomalien (Read phenomena)
  • Transaction-Isolation-Levels
  • ACID

Anomalien bezeichnen ein unerwartetes oder unwillkommenes Verhalten beim Lesen.

„Dirty Read“ bedeutet, dass auf Daten zugegriffen werden kann, die in einer anderen Transaktion geändert werden, bevor sie committed worden sind. Das widerspricht jeder intuitiven Vorstellung von Transaktionen. Eine interessante Frage ist aber, ob die Transaktion, die die Daten verändert, bei ihren Zugriffen jeweils den Stand nach den eigenen Veränderungen sehen kann oder ob dies erst nach dem eigenen Commit möglich ist. Bei Oracle galt immer der erste Fall, außer es hat sich kürzlich geändert, was ich aber stark bezweifle.

„Non-repeatable read“ steht für Leseoperationen auf denselben Daten während einer Transaktion, die aufgrund in anderen Transaktionen vorgenommener Änderungen verschiedene Ergebnisse liefern. Das ist genau das Problem mit dem großen Höraal. Oracle vermeidet das zum Beispiel standardmäßig.

„Phantom reads“ stehen für Leseoperationen, bei denen zwar Daten sich selbst nicht ändern können, aber durch DELETE und INSERT Datensätze wegfallen oder hinzukommen können.

Isolationslevel bezeichnen eine Einstellung einer Datenbank und die Anforderungen dazu.

„Read Uncommited“ erlaubt „dirty read“. Mir ist kein Fall bekannt, in dem das tatsächlich so praktiziert wird. Diese Einstellung ist für parallel mehrfach verwendete Datenbanken kaum brauchbar. Man kann das konfigurieren und der Standard erlaubt Datenbanken „besser“ als das zu sein.

„Read Committed“ steht dafür, dass man nur Daten von vollständig abgeschlossenen Transaktionen sehen kann. Das bedeutet also, das „dirty read“ ausgeschlossen ist, aber „non-repeatable read“ möglich ist. Das ist die Grundeinstellung für viele Datenbanken, z.B. für PostgresSQL.

„Repeatable Reads“ steht für eine Einstellung, die die Daten gegenüber UPDATEs scheinbar konstant hält. Das unterbindet „non-repeatable-read“, aber nicht „phantom-read“.

Bei der Einstellung „Serializable“ werden alle Transaktionen komplett entflochten. Man bekommt das Verhalten als wären alle Transaktionen in einer Warteschlange (Queue) und es würde immer nur jeweils eine nach der anderen abgearbeitet, womit wir wieder am Anfang wären, aber das stimmt nicht ganz. Zunächst mal werden sogar phantom-reads unterbunden.

Reale Datenbanken

Reale-Datenbanken haben eine Standardeinstellung („default“) für die Transaktionsisolation, aber sie dürfen immer besser als der eingestellte Wert sein, ohne den Standard zu verletzen. Das gibt also die Möglichkeit, Mischformen zu implementieren. Oracle hat mit „read-only“ sogar einen zusätzlichen Wert.

Nun gibt es verschiedene Wege, so etwas zu implementieren, grob gesagt sind es Locking oder Multiversion. Beim Locking werden Datensätze, Bereich, Spalten, Tabellen oder ganze Schemata gelockt, also exklusiv von einer Transaktion in Anspruch genommen. Bei Multiversion werden verschiedene Kopien der Daten erstellt und jeweils mit diesen gearbeitet. Es bleibt eine Herausforderung, die Daten am Schluss zusammenzufügen.

Verschiedene Datenbankprodukte nutzen den Interpretationsspielraum des Standards recht gut aus. Z.B. ist Oracle natürlich der Standard und man muss nur so tun, als halte man sich an den SQL-Standard.

Ich empfehle am Anfang eines Projekts eine weise Entscheidung über die Wahl der Datenbank zu treffen. Gerade hier bei den Transaktions-Isolations-Levels zeigt sich einmal mehr, dass die verschiedenen Datenbanken nicht so gleich sind und deshalb der Astausch gegen ein anderes Produkt problematisch und teuer sein kann.

ACID

ACID ist das englische Wort für Säure. In der Chemie nennt man Substanzen mit einen pH-Wert < 7 Säuren.
Hier ist aber „ACID“ ein Acronym:

  • Atomicity
  • Consistency
  • Isolation
  • Durability

Ein paar Links

Share Button

Beteilige dich an der Unterhaltung

1 Kommentar

Schreibe einen Kommentar

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

*