Das InterSystems IRIS DBMS unterstützt interessante Strukturen zur Speicherung von Daten – Globals. Im Wesentlichen handelt es sich hierbei um mehrstufige Schlüssel mit diversen zusätzlichen Extras in Form von Transaktionen, schnellen Funktionen zum Durchqueren von Datenbäumen, Sperren und einer eigenen ObjectScript-Sprache.
Lesen Sie mehr über Globals in der Artikelserie „Globals sind Schatzschwerter zum Speichern von Daten“:
Ich begann mich dafür zu interessieren, wie Transaktionen in Globals implementiert werden und welche Funktionen es gibt. Schließlich handelt es sich hier um eine völlig andere Struktur zum Speichern von Daten als bei den üblichen Tabellen. Viel niedrigeres Niveau.
Wie aus der Theorie relationaler Datenbanken bekannt ist, muss eine gute Implementierung von Transaktionen den Anforderungen genügen :
A – Atomar (Atomizität). Alle in der Transaktion vorgenommenen Änderungen oder gar keine werden protokolliert.
C – Konsistenz. Nach Abschluss einer Transaktion muss der logische Zustand der Datenbank intern konsistent sein. Diese Anforderung betrifft in vielerlei Hinsicht den Programmierer, bei SQL-Datenbanken betrifft sie jedoch auch Fremdschlüssel.
Ich – Isolieren. Parallel laufende Transaktionen sollten sich nicht gegenseitig beeinflussen.
D – Langlebig. Nach erfolgreichem Abschluss einer Transaktion sollten Probleme auf niedrigeren Ebenen (z. B. Stromausfall) keinen Einfluss auf die durch die Transaktion geänderten Daten haben.
Globals sind nicht relationale Datenstrukturen. Sie wurden entwickelt, um auf sehr begrenzter Hardware superschnell zu laufen. Schauen wir uns die Implementierung von Transaktionen in Globals mit an .
Zur Unterstützung von Transaktionen in IRIS werden die folgenden Befehle verwendet: , , .
1. Atomarität
Der einfachste Weg, dies zu überprüfen, ist die Atomizität. Wir überprüfen von der Datenbankkonsole aus.
Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMITDann schließen wir:
Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)Wir bekommen:
1 2 3Alles in Ordnung. Die Atomarität bleibt erhalten: Alle Änderungen werden aufgezeichnet.
Lassen Sie uns die Aufgabe verkomplizieren, einen Fehler einführen und sehen, wie die Transaktion teilweise oder gar nicht gespeichert wird.
Überprüfen wir noch einmal die Atomizität:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3Dann werden wir den Container gewaltsam stoppen, starten und sehen.
docker kill my-irisDieser Befehl entspricht fast einem erzwungenen Herunterfahren, da er ein SIGKILL-Signal sendet, um den Prozess sofort zu stoppen.
Möglicherweise wurde die Transaktion teilweise gespeichert?
WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)- Nein, es hat nicht überlebt.
Versuchen wir es mit dem Rollback-Befehl:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TROLLBACK
WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)Es ist auch nichts erhalten geblieben.
2. Konsistenz
Da in Datenbanken, die auf Globals basieren, Schlüssel auch auf Globals erstellt werden (ich möchte Sie daran erinnern, dass ein Global eine Struktur auf niedrigerer Ebene zum Speichern von Daten ist als eine relationale Tabelle), muss zur Erfüllung der Konsistenzanforderung eine Änderung des Schlüssels vorgenommen werden in der gleichen Transaktion wie eine Änderung des Global.
Wir haben zum Beispiel eine globale ^person, in der wir Persönlichkeiten speichern und die TIN als Schlüssel verwenden.
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...Um eine schnelle Suche nach Nachnamen und Vornamen zu ermöglichen, haben wir den Indexschlüssel ^ erstellt.
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1Damit die Datenbank konsistent ist, müssen wir die Persona wie folgt hinzufügen:
TSTART
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
TCOMMITDementsprechend müssen wir beim Löschen auch eine Transaktion verwenden:
TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMITMit anderen Worten: Die Erfüllung der Konsistenzanforderung liegt vollständig auf den Schultern des Programmierers. Aber wenn es um Globals geht, ist das normal, da sie auf niedrigem Niveau sind.
3. Isolation
Hier beginnt die Wildnis. Viele Benutzer arbeiten gleichzeitig an derselben Datenbank und ändern dieselben Daten.
Die Situation ist vergleichbar damit, dass viele Benutzer gleichzeitig mit demselben Code-Repository arbeiten und versuchen, gleichzeitig Änderungen an vielen Dateien vorzunehmen.
Die Datenbank sollte alles in Echtzeit klären. Wenn man bedenkt, dass es in seriösen Unternehmen sogar eine spezielle Person gibt, die für die Versionskontrolle (zum Zusammenführen von Zweigen, Lösen von Konflikten usw.) verantwortlich ist und die Datenbank dies alles in Echtzeit erledigen muss, ist die Komplexität der Aufgabe und die Richtigkeit der Datenbankdesign und Code, der ihm dient.
Die Datenbank kann die Bedeutung der von Benutzern durchgeführten Aktionen nicht verstehen, um Konflikte zu vermeiden, wenn sie an denselben Daten arbeiten. Es kann nur eine Transaktion, die mit einer anderen in Konflikt steht, rückgängig gemacht oder nacheinander ausgeführt werden.
Ein weiteres Problem besteht darin, dass während der Ausführung einer Transaktion (vor einem Commit) der Status der Datenbank inkonsistent sein kann. Daher ist es wünschenswert, dass andere Transaktionen keinen Zugriff auf den inkonsistenten Status der Datenbank haben, was in relationalen Datenbanken erreicht wird auf viele Arten: Erstellen von Snapshots, Multiversionierung von Zeilen usw.
Bei der parallelen Ausführung von Transaktionen ist es uns wichtig, dass diese sich nicht gegenseitig beeinträchtigen. Dies ist die Eigenschaft der Isolation.
SQL definiert 4 Isolationsstufen:
- LESEN SIE UNVERBINDLICH
- LESEN SIE ENGAGIERT
- WIEDERHOLBARES LESEN
- SERIALISIERBAR
Schauen wir uns jede Ebene einzeln an. Die Kosten für die Implementierung jeder Ebene steigen nahezu exponentiell.
LESEN SIE UNVERBINDLICH - Dies ist die niedrigste Isolationsstufe, aber gleichzeitig die schnellste. Transaktionen können voneinander vorgenommene Änderungen lesen.
LESEN SIE ENGAGIERT ist die nächste Stufe der Isolation, die einen Kompromiss darstellt. Transaktionen können vor dem Commit keine gegenseitigen Änderungen lesen, sie können jedoch alle nach dem Commit vorgenommenen Änderungen lesen.
Wenn wir eine lange Transaktion T1 haben, bei der Commits in den Transaktionen T2, T3 ... Tn stattfanden, die mit den gleichen Daten wie T1 funktionierten, dann erhalten wir bei der Datenanforderung in T1 jedes Mal ein anderes Ergebnis. Dieses Phänomen wird als nicht wiederholbares Lesen bezeichnet.
WIEDERHOLBARES LESEN — Auf dieser Isolationsstufe tritt das Phänomen des nicht wiederholbaren Lesens nicht auf, da für jede Anforderung zum Lesen von Daten ein Snapshot der Ergebnisdaten und bei Wiederverwendung in derselben Transaktion die Daten aus dem Snapshot erstellt werden wird eingesetzt. Auf dieser Isolationsstufe ist es jedoch möglich, Phantomdaten zu lesen. Dies bezieht sich auf das Lesen neuer Zeilen, die durch parallel festgeschriebene Transaktionen hinzugefügt wurden.
SERIALISIERBAR — das höchste Maß an Isolierung. Es zeichnet sich dadurch aus, dass in einer Transaktion in irgendeiner Weise verwendete Daten (Lesen oder Ändern) für andere Transaktionen erst nach Abschluss der ersten Transaktion verfügbar werden.
Lassen Sie uns zunächst herausfinden, ob Vorgänge in einer Transaktion vom Hauptthread isoliert sind. Öffnen wir zwei Terminalfenster.
Kill ^t
Write ^t(1)
2
TSTART
Set ^t(1)=2Es gibt keine Isolation. Ein Thread sieht, was der zweite Thread tut, der die Transaktion eröffnet hat.
Mal sehen, ob Transaktionen verschiedener Threads sehen, was in ihnen passiert.
Öffnen wir zwei Terminalfenster und öffnen wir zwei Transaktionen parallel.
kill ^t
TSTART
Write ^t(1)
3
TSTART
Set ^t(1)=3
Parallele Transaktionen sehen die Daten des jeweils anderen. Wir haben also die einfachste, aber auch schnellste Isolationsstufe erhalten: READ UNCOMMITED.
Im Prinzip wäre dies für Globals zu erwarten, bei denen Leistung schon immer im Vordergrund stand.
Was wäre, wenn wir bei Operationen auf Globals ein höheres Maß an Isolation benötigen?
Hier gilt es darüber nachzudenken, warum es überhaupt Isolationsstufen braucht und wie diese funktionieren.
Die höchste Isolationsstufe, SERIALIZE, bedeutet, dass das Ergebnis parallel ausgeführter Transaktionen ihrer sequentiellen Ausführung entspricht, was die Vermeidung von Kollisionen garantiert.
Wir können dies mithilfe intelligenter Sperren in ObjectScript tun, die viele verschiedene Verwendungszwecke haben: Mit dem Befehl können Sie regelmäßige, inkrementelle und mehrfache Sperren durchführen .
Niedrigere Isolationsstufen sind Kompromisse, die darauf abzielen, die Datenbankgeschwindigkeit zu erhöhen.
Sehen wir uns an, wie wir mithilfe von Sperren unterschiedliche Isolationsstufen erreichen können.
Mit diesem Operator können Sie nicht nur exklusive Sperren verwenden, die zum Ändern von Daten erforderlich sind, sondern auch sogenannte gemeinsam genutzte Sperren, die mehrere Threads parallel verwenden können, wenn diese Daten lesen müssen, die während des Lesevorgangs nicht von anderen Prozessen geändert werden sollen.
Weitere Informationen zur Zwei-Phasen-Blockierungsmethode auf Russisch und Englisch:
→
→
Die Schwierigkeit besteht darin, dass während einer Transaktion der Status der Datenbank möglicherweise inkonsistent ist, diese inkonsistenten Daten jedoch für andere Prozesse sichtbar sind. Wie kann man das vermeiden?
Mithilfe von Sperren erstellen wir Sichtbarkeitsfenster, in denen der Status der Datenbank konsistent ist. Und jeder Zugang zu solchen Sichtfenstern des vereinbarten Zustands wird durch Schlösser kontrolliert.
Gemeinsame Sperren für dieselben Daten sind wiederverwendbar – mehrere Prozesse können sie verwenden. Diese Sperren verhindern, dass andere Prozesse Daten ändern, d. h. Sie werden verwendet, um Fenster mit konsistentem Datenbankstatus zu bilden.
Für Datenänderungen werden exklusive Sperren verwendet – nur ein Prozess kann eine solche Sperre annehmen. Eine exklusive Sperre kann erfolgen durch:
- Jeder Prozess, wenn die Daten frei sind
- Nur der Prozess, der über eine gemeinsame Sperre für diese Daten verfügt und als erster eine exklusive Sperre angefordert hat.

Je schmaler das Sichtbarkeitsfenster, desto länger müssen andere Prozesse darauf warten, aber desto konsistenter kann der Zustand der darin enthaltenen Datenbank sein.
READ_COMMITTED – Das Wesentliche dieser Ebene ist, dass wir nur festgeschriebene Daten von anderen Threads sehen. Wenn die Daten in einer anderen Transaktion noch nicht festgeschrieben wurden, sehen wir die alte Version.
Dadurch können wir die Arbeit parallelisieren, anstatt auf die Freigabe der Sperre warten zu müssen.
Ohne spezielle Tricks können wir die alte Version der Daten in IRIS nicht sehen und müssen uns daher mit Sperren begnügen.
Dementsprechend müssen wir gemeinsame Sperren verwenden, um das Lesen von Daten nur in Momenten der Konsistenz zu ermöglichen.
Nehmen wir an, wir haben eine Benutzerbasis, die sich gegenseitig Geld überweist.
Zeitpunkt der Übergabe von Person 123 an Person 242:
LOCK +^person(123), +^person(242)
Set ^person(123, amount) = ^person(123, amount) - amount
Set ^person(242, amount) = ^person(242, amount) + amount
LOCK -^person(123), -^person(242)Der Zeitpunkt der Anforderung des Geldbetrags von Person 123 vor der Abbuchung muss (standardmäßig) mit einer exklusiven Sperre einhergehen:
LOCK +^person(123)
Write ^person(123)Und wenn Sie den Kontostatus in Ihrem persönlichen Konto anzeigen müssen, können Sie eine gemeinsame Sperre verwenden oder diese überhaupt nicht verwenden:
LOCK +^person(123)#”S”
Write ^person(123)Wenn wir jedoch davon ausgehen, dass Datenbankoperationen fast sofort ausgeführt werden (ich möchte Sie daran erinnern, dass Globals eine viel niedrigere Struktur als eine relationale Tabelle sind), dann sinkt der Bedarf an dieser Ebene.
WIEDERHOLBARES LESEN – Diese Isolationsstufe ermöglicht das mehrfache Lesen von Daten, die durch gleichzeitige Transaktionen geändert werden können.
Dementsprechend müssen wir eine gemeinsame Sperre für das Lesen der von uns geänderten Daten und eine exklusive Sperre für die von uns geänderten Daten einrichten.
Glücklicherweise ermöglicht Ihnen der LOCK-Operator, alle erforderlichen Sperren, von denen es viele geben kann, in einer Anweisung detailliert aufzulisten.
LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)andere Vorgänge (zu diesem Zeitpunkt versuchen parallele Threads, ^person(123, Menge) zu ändern, können dies jedoch nicht)
LOCK +^person(123, amount)
изменение ^person(123, amount)
LOCK -^person(123, amount)
чтение ^person(123, amount)
LOCK -^person(123, amount)#”S”Wenn Sie durch Kommas getrennte Sperren auflisten, werden diese nacheinander ausgeführt. Wenn Sie dies jedoch tun:
LOCK +(^person(123),^person(242))dann werden sie alle auf einmal atomar genommen.
SERIALISIEREN – Wir müssen Sperren setzen, damit letztendlich alle Transaktionen, die gemeinsame Daten haben, nacheinander ausgeführt werden. Bei diesem Ansatz sollten die meisten Sperren exklusiv sein und aus Leistungsgründen auf die kleinsten Bereiche des globalen Netzwerks angewendet werden.
Wenn wir über die Abbuchung von Geldern in der globalen ^person sprechen, dann ist dafür nur die Isolationsstufe SERIALIZE akzeptabel, da das Geld streng nacheinander ausgegeben werden muss, andernfalls ist es möglich, den gleichen Betrag mehrmals auszugeben.
4. Haltbarkeit
Ich habe Tests mit hartem Schneiden des Behälters durchgeführt
docker kill my-irisDie Basis hat sie gut vertragen. Es wurden keine Probleme festgestellt.
Fazit
Für Globals bietet InterSystems IRIS Transaktionsunterstützung. Sie sind wirklich atomar und zuverlässig. Um die Konsistenz einer auf Globals basierenden Datenbank sicherzustellen, sind Programmieraufwand und die Verwendung von Transaktionen erforderlich, da sie nicht über komplexe integrierte Konstrukte wie Fremdschlüssel verfügt.
Die Isolationsstufe von Globals ohne Verwendung von Sperren ist READ UNCOMMITED und kann bei Verwendung von Sperren bis zur SERIALIZE-Stufe sichergestellt werden.
Die Korrektheit und Geschwindigkeit von Transaktionen auf Globals hängt stark von den Fähigkeiten des Programmierers ab: Je mehr gemeinsam genutzte Sperren beim Lesen verwendet werden, desto höher ist der Isolationsgrad und je enger exklusive Sperren verwendet werden, desto schneller ist die Leistung.
Source: habr.com
