Transaktionen und mehr

Verfügbar

5.1 Die Frage der Gleichzeitigkeit

Beginnen wir mit einer etwas entfernten Theorie.

Jedes von Programmierern erstellte Informationssystem (oder einfach eine Anwendung) besteht aus mehreren typischen Blöcken, von denen jeder einen Teil der erforderlichen Funktionalität bereitstellt. Der Cache wird beispielsweise verwendet, um sich das Ergebnis eines ressourcenintensiven Vorgangs zu merken, um ein schnelleres Lesen von Daten durch den Client zu gewährleisten, Stream-Verarbeitungstools ermöglichen das Senden von Nachrichten an andere Komponenten zur asynchronen Verarbeitung und Stapelverarbeitungstools werden verwendet, um „ „Harken“ der angesammelten Datenmengen mit einer gewissen Periodizität. .

Und in fast jeder Anwendung sind Datenbanken (DBs) auf die eine oder andere Weise beteiligt, die normalerweise zwei Funktionen erfüllen: Daten speichern, wenn sie von Ihnen empfangen werden, und sie Ihnen später auf Anfrage zur Verfügung stellen. Selten kommt jemand auf die Idee, eine eigene Datenbank zu erstellen, da es bereits viele fertige Lösungen gibt. Doch wie wählen Sie das Richtige für Ihre Anwendung aus?

Stellen wir uns also vor, dass Sie eine Anwendung mit einer mobilen Schnittstelle geschrieben haben, die es Ihnen ermöglicht, eine zuvor gespeicherte Liste von Aufgaben rund um das Haus zu laden – also aus der Datenbank zu lesen, sie mit neuen Aufgaben zu ergänzen und jede einzelne zu priorisieren Aufgabe - von 1 (höchste) bis 3 (niedrigste). Nehmen wir an, Ihre mobile Anwendung wird jeweils nur von einer Person verwendet. Aber jetzt hast du es gewagt, deiner Mutter von deiner Kreation zu erzählen, und jetzt ist sie die zweite regelmäßige Benutzerin. Was passiert, wenn Sie gleichzeitig, genau in derselben Millisekunde, beschließen, einer Aufgabe – „Fenster putzen“ – eine andere Priorität zuzuweisen?

In professioneller Hinsicht können die Datenbankabfragen von Ihnen und Ihrer Mutter als zwei Prozesse betrachtet werden, die eine Abfrage an die Datenbank gestellt haben. Ein Prozess ist eine Einheit in einem Computerprogramm, die in einem oder mehreren Threads ausgeführt werden kann. Normalerweise verfügt ein Prozess über ein Maschinencode-Image, Speicher, Kontext und andere Ressourcen. Mit anderen Worten kann der Prozess als die Ausführung von Programmanweisungen auf dem Prozessor charakterisiert werden. Wenn Ihre Anwendung eine Anfrage an die Datenbank stellt, sprechen wir davon, dass Ihre Datenbank die über das Netzwerk von einem Prozess empfangene Anfrage verarbeitet. Wenn zwei Benutzer gleichzeitig in der Anwendung sitzen, kann es zu einem bestimmten Zeitpunkt zwei Prozesse geben.

Wenn ein Prozess eine Anfrage an die Datenbank stellt, findet er sie in einem bestimmten Zustand vor. Ein zustandsbehaftetes System ist ein System, das sich an frühere Ereignisse erinnert und einige Informationen speichert, die als „Zustand“ bezeichnet werden. Eine als deklarierte Variable integerkann den Status 0, 1, 2 oder beispielsweise 42 haben. Mutex (gegenseitiger Ausschluss) hat zwei Zustände: gesperrt oder entsperrt , genau wie ein binäres Semaphor („erforderlich“ vs. „freigegeben“) und im Allgemeinen binär (binäre) Datentypen und Variablen, die nur zwei Zustände haben können – 1 oder 0.

Auf dem Konzept des Zustands basieren mehrere mathematische und technische Strukturen, wie zum Beispiel ein endlicher Automat – ein Modell, das einen Eingang und einen Ausgang hat und sich zu jedem Zeitpunkt in einem einer endlichen Menge von Zuständen befindet – und der „Zustand“. „Entwurfsmuster, bei dem ein Objekt sein Verhalten abhängig vom internen Zustand ändert (z. B. abhängig davon, welcher Wert der einen oder anderen Variablen zugewiesen ist).

Daher haben die meisten Objekte in der Maschinenwelt einen Zustand, der sich im Laufe der Zeit ändern kann: unsere Pipeline, die ein großes Datenpaket verarbeitet, einen Fehler auslöst und fehlschlägt, oder die Wallet-Objekteigenschaft, die den im Konto des Benutzers verbleibenden Geldbetrag speichert Konto, Änderungen nach Lohn- und Gehaltsabrechnungen.

Ein Übergang („Übergang“) von einem Zustand in einen anderen – beispielsweise von „ in Bearbeitung“ zu „ fehlgeschlagen “ – wird als Operation bezeichnet. Wahrscheinlich kennt jeder die CRUD- Operationen – , create, readoder ähnliche HTTP- Methoden – , , , update. Aber Programmierer geben Operationen in ihrem Code oft andere Namen, weil die Operation komplexer sein kann als nur das Lesen eines bestimmten Werts aus der Datenbank – sie kann auch die Daten überprüfen, und dann unsere Operation, die die Form einer Funktion angenommen hat, wird zum Beispiel heißen: Und wer führt diese Operationsfunktionen aus? bereits beschriebene Prozesse.deletePOSTGETPUTDELETEvalidate()

Noch ein bisschen, und Sie werden verstehen, warum ich die Begriffe so ausführlich beschreibe!

Jede Operation – sei es eine Funktion oder in verteilten Systemen das Senden einer Anfrage an einen anderen Server – hat zwei Eigenschaften: die Aufrufzeit und die Abschlusszeit (Completion Time) , die streng genommen größer ist als die Aufrufzeit (Forscher von Jepsen). gehen von der theoretischen Annahme aus, dass diese beiden Zeitstempel mit imaginären, vollständig synchronisierten, global verfügbaren Uhren versehen werden.

Stellen wir uns unsere To-Do-Listen-Anwendung vor. Sie stellen über die mobile Schnittstelle in eine Anfrage an die Datenbank 14:00:00.014, und Ihre Mutter 13:59:59.678aktualisiert (d. h. 336 Millisekunden zuvor) die To-Do-Liste über dieselbe Schnittstelle und fügt ihr das Abwaschen von Geschirr hinzu. Unter Berücksichtigung der Netzwerkverzögerung und der möglichen Aufgabenwarteschlange für Ihre Datenbank kann die Datenbank, wenn außer Ihnen und Ihrer Mutter auch alle Freunde Ihrer Mutter Ihre Anwendung verwenden, die Anfrage Ihrer Mutter ausführen, nachdem sie Ihre Anfrage verarbeitet hat. Mit anderen Worten: Es besteht die Möglichkeit, dass zwei Ihrer Anfragen sowie Anfragen der Freundinnen Ihrer Mutter gleichzeitig (gleichzeitig) an dieselben Daten gesendet werden.

Damit sind wir beim wichtigsten Begriff im Bereich Datenbanken und verteilten Anwendungen angelangt – Parallelität. Was genau kann die Gleichzeitigkeit zweier Vorgänge bedeuten? Wenn eine Operation T1 und eine Operation T2 gegeben sind, dann gilt:

  • T1 kann vor der Startzeit der Ausführung T2 gestartet und zwischen der Start- und Endzeit von T2 beendet werden
  • T2 kann vor der Startzeit von T1 gestartet und zwischen dem Start und dem Ende von T1 beendet werden
  • T1 kann zwischen der Start- und Endzeit der T1-Ausführung gestartet und beendet werden
  • und jedes andere Szenario, in dem T1 und T2 eine gemeinsame Ausführungszeit haben

Es ist klar, dass wir im Rahmen dieser Vorlesung hauptsächlich über Anfragen sprechen, die in die Datenbank eingehen und wie das Datenbankverwaltungssystem diese Anfragen wahrnimmt, aber der Begriff Parallelität ist beispielsweise im Kontext von Betriebssystemen wichtig. Ich werde nicht zu sehr vom Thema dieses Artikels abweichen, aber ich denke, es ist wichtig zu erwähnen, dass die Parallelität, über die wir hier sprechen, nicht mit dem Dilemma von Parallelität und Parallelität und ihren Unterschieden zusammenhängt, die im Kontext diskutiert werden von Betriebssystemen und High-Performance-Computing. Parallelität ist eine Möglichkeit, Parallelität in einer Umgebung mit mehreren Kernen, Prozessoren oder Computern zu erreichen. Wir sprechen von Parallelität im Sinne des gleichzeitigen Zugriffs verschiedener Prozesse auf gemeinsame Daten.

Und was kann eigentlich rein theoretisch schief gehen?

Bei der Arbeit an gemeinsam genutzten Daten können zahlreiche Probleme im Zusammenhang mit der Parallelität, auch „Race Conditions“ genannt, auftreten. Das erste Problem tritt auf, wenn ein Prozess Daten empfängt, die er nicht hätte empfangen sollen: unvollständige, temporäre, abgebrochene oder anderweitig „falsche“ Daten. Das zweite Problem besteht darin, dass der Prozess veraltete Daten empfängt, also Daten, die nicht dem zuletzt gespeicherten Zustand der Datenbank entsprechen. Nehmen wir an, eine Anwendung hat Geld vom Konto eines Benutzers mit einem Nullsaldo abgebucht, weil die Datenbank den Kontostatus an die Anwendung zurückgegeben hat, ohne die letzte Geldabhebung von diesem Konto zu berücksichtigen, die erst vor ein paar Millisekunden erfolgte. Die Situation ist mittelmäßig, nicht wahr?

5.2 Transaktionen kamen, um uns zu retten

Um solche Probleme zu lösen, entstand das Konzept einer Transaktion – eine bestimmte Gruppe aufeinanderfolgender Operationen (Zustandsänderungen) mit einer Datenbank, die logisch eine einzelne Operation darstellt. Ich werde noch einmal ein Beispiel mit einer Bank nennen – und das nicht zufällig, denn der Begriff einer Transaktion tauchte offenbar gerade im Zusammenhang mit der Arbeit mit Geld auf. Das klassische Beispiel einer Transaktion ist die Überweisung von Geld von einem Bankkonto auf ein anderes: Sie müssen den Betrag zunächst vom Quellkonto abheben und ihn dann auf das Zielkonto einzahlen.

Damit diese Transaktion durchgeführt werden kann, muss die Anwendung mehrere Aktionen in der Datenbank ausführen: den Kontostand des Absenders prüfen, den Betrag auf dem Konto des Absenders sperren, den Betrag dem Konto des Empfängers hinzufügen und den Betrag vom Absender abbuchen. Für eine solche Transaktion gelten mehrere Voraussetzungen. Beispielsweise kann die Anwendung keine veralteten oder falschen Informationen über den Kontostand erhalten – beispielsweise wenn gleichzeitig eine Paralleltransaktion auf halbem Weg mit einem Fehler endete und der Betrag nicht vom Konto abgebucht wurde – und unsere Anwendung bereits Informationen erhalten hat dass die Mittel abgeschrieben wurden.

Um dieses Problem zu lösen, wurde eine Eigenschaft einer Transaktion wie „Isolation“ herangezogen: Unsere Transaktion wird so ausgeführt, als ob im selben Moment keine anderen Transaktionen ausgeführt würden. Unsere Datenbank führt gleichzeitige Vorgänge so aus, als würde sie sie nacheinander und nacheinander ausführen tatsächlich heißt die höchste Isolationsstufe Strict Serializable . Ja, die höchste, was bedeutet, dass es mehrere Ebenen gibt.

„Hör auf“, sagst du. Behalten Sie Ihre Pferde, Sir.

Erinnern wir uns daran, wie ich beschrieben habe, dass jede Operation eine Aufrufzeit und eine Ausführungszeit hat. Der Einfachheit halber können Sie das Aufrufen und Ausführen als zwei Aktionen betrachten. Dann kann die sortierte Liste aller Aufruf- und Ausführungsaktionen als Verlauf der Datenbank bezeichnet werden. Dann ist die Transaktionsisolationsstufe eine Reihe von Historien. Wir verwenden Isolationsstufen, um zu bestimmen, welche Geschichten „gut“ sind. Wenn wir sagen, dass eine Geschichte „die Serialisierbarkeit unterbricht“ oder „nicht serialisierbar ist“, meinen wir, dass die Geschichte nicht zur Menge der serialisierbaren Geschichten gehört.

Um zu verdeutlichen, um welche Art von Geschichten es sich handelt, gebe ich Beispiele. Es gibt zum Beispiel eine solche Art von Geschichte – Zwischenlesen . Dies geschieht, wenn Transaktion A Daten aus einer Zeile lesen darf, die von einer anderen laufenden Transaktion B geändert wurde und noch nicht festgeschrieben wurde („nicht festgeschrieben“) – das heißt, die Änderungen wurden tatsächlich noch nicht endgültig festgeschrieben Transaktion B und kann diese jederzeit stornieren. Und zum Beispiel ist „Aborted Read“ nur unser Beispiel für eine abgebrochene Auszahlungstransaktion

Es gibt mehrere mögliche Anomalien. Das heißt, Anomalien sind eine Art unerwünschter Datenzustand, der beim kompetitiven Zugriff auf die Datenbank auftreten kann. Und um bestimmte unerwünschte Zustände zu vermeiden, verwenden Datenbanken unterschiedliche Isolationsstufen – also unterschiedliche Ebenen des Datenschutzes vor unerwünschten Zuständen. Diese Ebenen (4 Stück) wurden im ANSI SQL-92-Standard aufgeführt.

Die Beschreibung dieser Ebenen erscheint einigen Forschern vage und sie bieten ihre eigenen, detaillierteren Klassifizierungen an. Ich empfehle Ihnen, auf das bereits erwähnte Jepsen-Projekt sowie auf das Hermitage-Projekt zu achten, das genau klären soll, welche Isolationsstufen bestimmte DBMS wie MySQL oder PostgreSQL bieten. Wenn Sie die Dateien aus diesem Repository öffnen, können Sie sehen, welche Reihenfolge von SQL-Befehlen sie verwenden, um die Datenbank auf bestimmte Anomalien zu testen, und Sie können etwas Ähnliches für die Datenbanken tun, an denen Sie interessiert sind. Hier ist ein Beispiel aus dem Repository, um Ihr Interesse aufrechtzuerhalten:

-- Database: MySQL

-- Setup before test
create table test (id int primary key, value int) engine=innodb;
insert into test (id, value) values (1, 10), (2, 20);

-- Test the "read uncommited" isolation level on the "Intermediate Reads" (G1b) anomaly
set session transaction isolation level read uncommitted; begin; -- T1
set session transaction isolation level read uncommitted; begin; -- T2
update test set value = 101 where id = 1; -- T1
select * from test; -- T2. Shows 1 => 101
update test set value = 11 where id = 1; -- T1
commit; -- T1
select * from test; -- T2. Now shows 1 => 11
commit; -- T2

-- Result: doesn't prevent G1b

Es ist wichtig zu verstehen, dass Sie für dieselbe Datenbank in der Regel eine von mehreren Isolationsarten wählen können. Warum nicht die stärkste Isolierung wählen? Denn wie alles in der Informatik sollte die gewählte Isolationsstufe einem Kompromiss entsprechen, den wir eingehen wollen – in diesem Fall einem Kompromiss bei der Ausführungsgeschwindigkeit: Je stärker die Isolationsstufe, desto langsamer werden die Anfragen verarbeitet. Um zu verstehen, welche Isolationsstufe Sie benötigen, müssen Sie die Anforderungen für Ihre Anwendung verstehen. Und um zu verstehen, ob die von Ihnen gewählte Datenbank diese Stufe bietet, müssen Sie einen Blick in die Dokumentation werfen – für die meisten Anwendungen wird dies jedoch ausreichen Wenn Sie besonders hohe Anforderungen haben, ist es besser, einen Test zu vereinbaren, wie ihn die Jungs vom Hermitage-Projekt machen.

5.3 „I“ und andere Buchstaben in ACID

Isolation ist im Grunde das, was Menschen meinen, wenn sie allgemein über ACID sprechen. Und aus diesem Grund habe ich die Analyse dieses Akronyms isoliert begonnen und bin nicht der Reihe nach vorgegangen, wie es diejenigen tun, die versuchen, dieses Konzept zu erklären. Schauen wir uns nun die restlichen drei Buchstaben an.

Erinnern Sie sich noch einmal an unser Beispiel mit einer Banküberweisung. Eine Transaktion zur Überweisung von Geldern von einem Konto auf ein anderes umfasst einen Abhebungsvorgang vom ersten Konto und einen Auffüllvorgang vom zweiten Konto. Wenn der Auffüllvorgang des zweiten Kontos fehlgeschlagen ist, möchten Sie wahrscheinlich nicht, dass der Abhebungsvorgang vom ersten Konto durchgeführt wird. Mit anderen Worten: Entweder gelingt die Transaktion vollständig oder sie findet überhaupt nicht statt, kann aber nicht nur für einen Teil durchgeführt werden. Diese Eigenschaft wird „Atomizität“ genannt und ist in ACID ein „A“.

Wenn unsere Transaktion ausgeführt wird, überträgt sie wie jede andere Operation die Datenbank von einem gültigen Zustand in einen anderen. Einige Datenbanken bieten sogenannte Constraints an – also Regeln, die für die gespeicherten Daten gelten, beispielsweise hinsichtlich Primär- oder Sekundärschlüssel, Indizes, Standardwerte, Spaltentypen usw. Wenn wir also eine Transaktion durchführen, müssen wir sicher sein, dass alle diese Einschränkungen erfüllt werden.

Diese Garantie wird „Konsistenz“ genannt und ist ein Buchstabe Cin ACID (nicht zu verwechseln mit der Konsistenz aus der Welt der verteilten Anwendungen, über die wir später sprechen werden). ordersIch werde ein klares Beispiel für Konsistenz im Sinne von ACID geben: Eine Anwendung für einen Online-Shop möchte der Tabelle eine Zeile hinzufügen , und die ID aus der Tabelle product_idwird in der Spalte angezeigt – typisch .productsforeign key

Wenn das Produkt beispielsweise aus dem Sortiment und damit aus der Datenbank entfernt wurde, sollte der Zeileneinfügungsvorgang nicht stattfinden und wir erhalten eine Fehlermeldung. Diese Garantie ist meiner Meinung nach im Vergleich zu anderen etwas weit hergeholt – schon allein deshalb, weil die aktive Nutzung von Einschränkungen aus der Datenbank eine Verlagerung der Verantwortung für die Daten (sowie eine teilweise Verlagerung der Geschäftslogik, wenn wir darüber sprechen) bedeutet eine solche Einschränkung wie CHECK ) von der Anwendung auf die Datenbank, was, wie man jetzt sagt, einfach so ist.

Und schließlich bleibt es D– „Widerstand“ (Haltbarkeit). Ein Systemausfall oder ein sonstiger Ausfall sollte nicht zum Verlust von Transaktionsergebnissen oder Datenbankinhalten führen. Das heißt, wenn die Datenbank antwortet, dass die Transaktion erfolgreich war, bedeutet dies, dass die Daten im nichtflüchtigen Speicher aufgezeichnet wurden – beispielsweise auf einer Festplatte. Das bedeutet übrigens nicht, dass Sie die Daten bei der nächsten Leseanfrage sofort sehen.

Neulich habe ich mit DynamoDB von AWS (Amazon Web Services) gearbeitet und einige Daten zum Speichern gesendet. Nachdem ich eine Antwort HTTP 200(OK) oder so etwas in der Art erhalten hatte, beschloss ich, es zu überprüfen – und sah dies nicht Daten in der Datenbank für die nächsten 10 Sekunden. Das heißt, DynamoDB hat meine Daten festgeschrieben, aber nicht alle Knoten wurden sofort synchronisiert, um die neueste Kopie der Daten zu erhalten (obwohl sie sich möglicherweise im Cache befand). Hier haben wir erneut das Gebiet der Konsistenz im Kontext verteilter Systeme betreten, aber die Zeit, darüber zu sprechen, ist noch nicht gekommen.

Jetzt wissen wir also, was ACID-Garantien sind. Und wir wissen sogar, warum sie nützlich sind. Aber brauchen wir sie wirklich in jeder Anwendung? Und wenn nicht, wann genau? Bieten alle DBs diese Garantien, und wenn nicht, was bieten sie stattdessen?

Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare