Die InterSystems IRIS-Datenbank unterstĂŒtzt interessante Datenstrukturen â Global-Variablen. Im Grunde handelt es sich dabei um mehrstufige SchlĂŒssel mit verschiedenen zusĂ€tzlichen Funktionen wie Transaktionen, schnellen Methoden zur Navigierung in DatenbĂ€umen, Sperrmechanismen und einer eigenen Sprache, ObjectScript.
Mehr zu Global-Variablen finden Sie in der Artikelreihe âGlobal-Variablen â die SchĂ€tze der Datenspeicherungâ:
Ich war neugierig, wie Transaktionen in Global-Variablen umgesetzt sind und welche Besonderheiten es dabei gibt. SchlieĂlich handelt es sich um eine völlig andere Struktur zur Datenspeicherung als die uns vertrauten Tabellen â viel tiefer in der Ebene.
Wie aus der Theorie relationaler Datenbanken bekannt ist, sollte eine gute Implementierung von Transaktionen die Anforderungen erfĂŒllen :
A â Atomic (AtomaritĂ€t). Alle Ănderungen, die in einer Transaktion vorgenommen werden, mĂŒssen entweder vollstĂ€ndig erfasst oder gar nicht gespeichert werden.
C â Consistency (Konsistenz). Nach Abschluss der Transaktion muss der logische Zustand der Datenbank intern widerspruchsfrei sein. Dieses Erfordernis betrifft in hohem MaĂe den Programmierer, ist aber auch fĂŒr relationale SQL-Datenbanken relevant und betrifft die externen SchlĂŒssel.
I â Isolation (Isolation). Parallel ausgefĂŒhrte Transaktionen dĂŒrfen sich nicht gegenseitig beeinflussen.
D â Durable (BestĂ€ndigkeit). Nach dem erfolgreichen Abschluss einer Transaktion sollten Probleme auf unteren Ebenen (zum Beispiel StromausfĂ€lle) keinen Einfluss auf die durch die Transaktion geĂ€nderten Daten haben.
Globals sind nicht-relationale Datenstrukturen. Sie wurden fĂŒr eine extrem schnelle AusfĂŒhrung auf sehr begrenzter Hardware entwickelt. Lassen Sie uns die Implementierung von Transaktionen in Globals mithilfe von .
FĂŒr die UnterstĂŒtzung von Transaktionen in IRIS werden die folgenden Befehle verwendet: , , .
1. AtomaritÀt
Die ĂberprĂŒfung der AtomaritĂ€t ist am einfachsten. Wir ĂŒberprĂŒfen dies ĂŒber die Datenbankkonsole.
Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMITDann ziehen wir folgende Schlussfolgerung:
Write ^a(1), â â, ^a(2), â â, ^a(3)Wir erhalten:
1 2 3Alles in Ordnung. Die AtomaritĂ€t wurde gewahrt: Alle Ănderungen wurden gespeichert.
Lassen Sie uns die Aufgabe erschweren, einen Fehler einfĂŒhren und sehen, wie die Transaktion gespeichert wird, teilweise oder gar nicht.
ĂberprĂŒfen wir erneut die AtomaritĂ€t:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3Danach stoppen wir den Container gewaltsam, starten ihn neu und schauen nach.
docker kill my-irisDieser Befehl ist praktisch das Ăquivalent zu einem gewaltsamen Ausschalten des Stroms, da er ein sofortiges Stoppsignal fĂŒr den Prozess SIGKILL sendet.
Könnte die Transaktion teilweise gespeichert worden sein?
WRITE ^a(1), ^a(2), ^a(3)
^
^a(1)â Nein, es wurde nichts gespeichert.
Testen wir den 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)
^
^a(1)Es wurde ebenfalls nichts gespeichert.
2. Konsistenz
Da in den Globals die SchlĂŒssel ebenfalls auf den Globals basieren (ich erinnere daran, dass ein Global eine niedrigere Datenspeicherstruktur als eine relationale Tabelle ist), muss fĂŒr die Einhaltung der Konsistenz die Ănderung des SchlĂŒssels in dieselbe Transaktion einbezogen werden, wie die Ănderung des Globals.
Zum Beispiel haben wir ein Global ^person, in dem wir Personen speichern, und als SchlĂŒssel verwenden wir die Steueridentifikationsnummer.
^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 SchlĂŒssel ^index erstellt.
^index('Kamenev', 'Sergey', 1234567) = 1Damit die Datenbank konsistent bleibt, mĂŒssen wir die Person so hinzufĂŒgen:
TSTART
^person(1234567, âfirstnameâ) = âSergeyâ
^person(1234567, âlastnameâ) = âKamenevâ
^person(1234567, âphoneâ) = â+74995555555
^index(âKamenevâ, âSergeyâ, 1234567) = 1
TCOMMITDaher mĂŒssen wir bei einer Löschung ebenfalls eine Transaktion verwenden:
TSTART
Kill ^person(1234567)
ZKill ^index(âKamenevâ, âSergeyâ, 1234567)
TCOMMITMit anderen Worten, die Einhaltung des Konsistenzanspruchs liegt vollstÀndig in der Verantwortung des Programmierers. Wenn es um globale Variablen geht, ist das jedoch in Ordnung, aufgrund ihrer niedrigen Abstraktionsebene.
3. Isolation
Hier wird es kompliziert. Viele Benutzer arbeiten gleichzeitig an derselben Datenbank und Àndern dieselben Daten.
Die Situation ist vergleichbar mit vielen Benutzern, die gleichzeitig an demselben Code-Repository arbeiten und versuchen, gleichzeitig Ănderungen an vielen Dateien zu committen.
Die Datenbank muss all dies in Echtzeit bewĂ€ltigen. In ernsthaften Unternehmen gibt es sogar spezielle Personen, die fĂŒr die Versionskontrolle (fĂŒr das ZusammenfĂŒhren von Branches, das Lösen von Konflikten usw.) zustĂ€ndig sind, wĂ€hrend die Datenbank all dies in Echtzeit erledigen muss. Daher wird die KomplexitĂ€t der Aufgabe und die Richtigkeit des Datenbankdesigns sowie des Codes, der sie betreut, offensichtlich.
Die Datenbank kann den Sinn der von den Nutzern durchgefĂŒhrten Aktionen nicht verstehen, um Konflikte zu vermeiden, wenn sie an denselben Daten arbeiten. Sie kann lediglich eine Transaktion, die einer anderen widerspricht, zurĂŒcknehmen oder diese nacheinander ausfĂŒhren.
Ein weiteres Problem besteht darin, dass wĂ€hrend der AusfĂŒhrung einer Transaktion (vor dem Commit) der Zustand der Datenbank inkonsistent sein kann. Daher ist es wĂŒnschenswert, dass andere Transaktionen keinen Zugriff auf einen inkonsistenten Zustand der Datenbank haben, was in relationalen Datenbanken auf verschiedene Weise erreicht wird: durch das Erstellen von Snapshots, Mehrversionen von Zeilen usw.
Bei der parallelen AusfĂŒhrung von Transaktionen ist es wichtig, dass sie sich nicht gegenseitig stören. Das ist das Isolationsmerkmal.
SQL definiert 4 Isolationsebenen:
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
Betrachten wir jede Ebene einzeln. Die Implementierungskosten jeder Ebene steigen beinahe exponentiell.
READ UNCOMMITTED â dies ist die niedrigste Isolationsebene, aber gleichzeitig die schnellste. Transaktionen können Ănderungen lesen, die von anderen vorgenommen wurden.
READ COMMITTED â dies ist die nĂ€chste Isolationsebene, die einen Kompromiss darstellt. Transaktionen können keine Ănderungen lesen, die von anderen vorgenommen wurden, bis diese festgeschrieben sind, aber sie können alle Ănderungen lesen, die nach dem Commit vorgenommen wurden.
Wenn wir eine lange Transaktion T1 haben, wÀhrend der Commits in den Transaktionen T2, T3 ⊠Tn stattfinden, die mit denselben Daten arbeiten wie T1, werden wir bei einer Abfrage von Daten in T1 jedes Mal ein anderes Ergebnis erhalten. Dieses PhÀnomen wird als nicht wiederholbare Lesung bezeichnet.
REPEATABLE READ â In diesem Isolationsniveau gibt es kein PhĂ€nomen des wiederholten Lesens, da fĂŒr jede Datenleseanfrage eine Momentaufnahme der Daten erstellt wird und bei der erneuten Verwendung in derselben Transaktion die Daten aus der Momentaufnahme verwendet werden. Allerdings ist in diesem Isolationsniveau das Lesen von Phantomdaten möglich. Das bedeutet das Lesen neuer Zeilen, die von parallel fixierten Transaktionen hinzugefĂŒgt wurden.
SERIALIZABLE â Die höchste Stufe der Isolation. Sie zeichnet sich dadurch aus, dass Daten, die in einer Transaktion auf irgendeine Weise verwendet werden (lesen oder Ă€ndern), fĂŒr andere Transaktionen erst nach Abschluss der ersten Transaktion verfĂŒgbar sind.
Lassen Sie uns zunĂ€chst herausfinden, ob es eine Isolation der Operationen in der Transaktion vom Hauptthread gibt. Ăffnen wir 2 Terminalfenster.
Kill ^t
Write ^t(1)
2
TSTART
Set ^t(1)=2Es gibt keine Isolation. Ein Thread sieht, was der andere, der die Transaktion eröffnet hat, macht.
Lassen Sie uns ĂŒberprĂŒfen, ob die Transaktionen unterschiedlicher Threads das sehen, was innerhalb von ihnen passiert.
Ăffnen wir 2 Terminalfenster und initiieren wir 2 Transaktionen parallel.
kill ^t
TSTART
Write ^t(1)
3
TSTART
Set ^t(1)=3
Parallele Transaktionen sehen einander die Daten. Daher haben wir die einfachste, aber auch schnellste Isolationsstufe READ UNCOMMITTED erreicht.
Das war grundsĂ€tzlich fĂŒr globale Transaktionen zu erwarten, bei denen die Leistung stets im Vordergrund steht.
Was ist, wenn wir benötigen, eine höhere Isolationsstufe fĂŒr die Operationen auf den globalen Transaktionen?
Hier sollten wir darĂŒber nachdenken, warum Isolationsstufen ĂŒberhaupt notwendig sind und wie sie funktionieren.
Die höchste Isolationsstufe SERIALIZE bedeutet, dass das Ergebnis parallel ausgefĂŒhrter Transaktionen dem sequenziellen Ablauf entspricht, was Kollisionen ausschlieĂt.
Das können wir mit geeigneten Sperren in ObjectScript erreichen, die zahlreiche Anwendungsmöglichkeiten bieten: man kann einfache, inkrementelle oder multiple Sperren mit dem Befehl .
Niedrigere Isolationsstufen sind Kompromisse, die dazu dienen, die Geschwindigkeit der Datenbank zu erhöhen.
Schauen wir uns an, wie wir verschiedene Isolationsstufen mit Hilfe von Sperren erreichen können.
Dieser Operator ermöglicht nicht nur exklusive Sperren, die fĂŒr die DatenĂ€nderung erforderlich sind, sondern auch sogenannte Shared-Sperren, die parallel von mehreren Threads genutzt werden können, wenn sie Daten lesen mĂŒssen, die von anderen Prozessen wĂ€hrend des Lesens nicht verĂ€ndert werden dĂŒrfen.
Mehr ĂŒber das Zwei-Phasen-Sperrverfahren in russischer und englischer Sprache:
â
â
Die Schwierigkeit besteht darin, dass der Zustand der Datenbank wĂ€hrend einer Transaktion inkonsistent sein kann, jedoch sind diese inkonsistenten Daten fĂŒr andere Prozesse sichtbar. Wie kann man das vermeiden?
Wir erstellen mithilfe von Sperren Sichtfenster, in denen der Datenbankzustand konsistent ist. Und alle Zugriffe auf solche Sichtfenster des konsistenten Zustands werden durch Sperren kontrolliert.
Shared-Sperren fĂŒr dieselben Daten sind wiederverwendbar â sie können von mehreren Prozessen beansprucht werden. Diese Sperren verhindern, dass andere Prozesse die Daten Ă€ndern, d.h. sie werden verwendet, um Fenster des konsistenten Datenbankzustands zu bilden.
Exklusive Sperren werden verwendet, um DatenĂ€nderungen zu steuern â eine solche Sperre kann nur von einem Prozess ĂŒbernommen werden. Eine exklusive Sperre kann ĂŒbernommen werden von:
- Jedem Prozess, wenn die Daten verfĂŒgbar sind
- Nur dem Prozess, der eine Shared-Sperre fĂŒr diese Daten hat und der als Erster eine exklusive Sperre angefordert hat.

Je enger das Sichtfenster, desto lĂ€nger mĂŒssen andere Prozesse warten, doch desto konsistenter kann der Zustand der Datenbank darin sein.
READ_COMMITED Der Kern dieses Niveaus ist, dass wir nur die von anderen Streams committeten Daten sehen. Wenn die Daten in einer anderen Transaktion noch nicht committet sind, sehen wir ihre alte Version.
Das ermöglicht uns, die Arbeit zu parallelisieren, anstatt auf das Freigeben der Sperre zu warten.
Ohne spezielle Tricks können wir die alte Datenversion in IRIS nicht sehen, daher mĂŒssen wir mit Sperren arbeiten.
Daher mĂŒssen wir mithilfe von Shared-Sperren das Lesen von Daten nur in Momenten der Konsistenz erlauben.
Angenommen, wir haben eine Benutzerdatenbank ^person, in der Nutzer Geld untereinander ĂŒberweisen.
Der Moment der Ăberweisung von Person 123 zu 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 Moment der Abfrage des Geldbetrags bei Person 123 vor der Abbuchung muss mit einer exklusiven Sperre (standardmĂ€Ăig) einhergehen:
LOCK +^person(123)
Write ^person(123)Wenn Sie den Kontostand im Kundenportal anzeigen mĂŒssen, können Sie eine gemeinsame Sperre verwenden oder sie sogar ganz weglassen:
LOCK +^person(123)#âSâ
Write ^person(123)Allerdings, wenn man annimmt, dass Datenbankoperationen nahezu sofort durchgefĂŒhrt werden (ich erinnere daran, dass Globals eine viel niedrigere Struktur sind als relationale Tabellen), sinkt die Notwendigkeit dieses Levels.
REPEATABLE READ â auf diesem Isolationslevel ist es zulĂ€ssig, dass mehrere LesevorgĂ€nge auf Daten durchgefĂŒhrt werden, die durch parallele Transaktionen geĂ€ndert werden können.
Deshalb mĂŒssen wir eine gemeinsame Sperre fĂŒr das Lesen von Daten setzen, die wir Ă€ndern, und exklusive Sperren fĂŒr die Daten, die wir Ă€ndern.
Zum GlĂŒck erlaubt der LOCK-Befehl, in einem einzigen Befehl alle erforderlichen Sperren detailliert aufzulisten, was sehr viele sein können.
LOCK +^person(123, amount)#âSâ
Lesen von ^person(123, amount)andere Operationen (parallel versuchen wÀhrenddessen andere Threads ^person(123, amount) zu Àndern, können dies jedoch nicht)
LOCK +^person(123, amount)
Ănderung von ^person(123, amount)
LOCK -^person(123, amount)
Lesen von ^person(123, amount)
LOCK -^person(123, amount)#âSâBei der Auflistung von Sperren durch Kommas werden diese nacheinander behandelt, und wenn man es so macht:
LOCK +(^person(123),^person(242))werden sie atomar alle sofort behandelt.
SERIALIZE â wir mĂŒssen die Sperren so setzen, dass am Ende alle Transaktionen, die gemeinsame Daten haben, nacheinander ausgefĂŒhrt werden. FĂŒr diesen Ansatz sollten die meisten Sperren exklusiv sein und auf die kleinsten Bereiche des Globals fĂŒr die Leistung gesetzt werden.
Spricht man jedoch von Abhebungen im Global ^person, ist nur das Isolationsniveau SERIALIZE akzeptabel, da Geld strikt nacheinander ausgegeben werden muss, sonst könnte der gleiche Betrag mehrere Male ausgegeben werden.
4. BestÀndigkeit
Ich habe Tests mit abruptem Herunterfahren des Containers durchgefĂŒhrt durch
docker kill my-irisDie Basis hat das gut verkraftet. Es wurden keine Probleme festgestellt.
Fazit
FĂŒr Globalen bietet InterSystems IRIS UnterstĂŒtzung fĂŒr Transaktionen. Diese sind tatsĂ€chlich atomar und zuverlĂ€ssig. Um jedoch die Konsistenz der Datenbank in diesen Globalen zu gewĂ€hrleisten, sind Programmieraufwand und der Einsatz von Transaktionen erforderlich, da es keine komplexen eingebauten Strukturen wie FremdschlĂŒssel gibt.
Das Isolationsniveau bei Globalen ohne Verwendung von Sperren ist READ UNCOMMITTED, wÀhrend bei Verwendung von Sperren auch SERIALIZE als Isolationsniveau erreicht werden kann.
Die Korrektheit und Geschwindigkeit von Transaktionen in Globalen hÀngen stark von der Kompetenz des Programmierers ab: Je breiter Shared-Sperren beim Lesen eingesetzt werden, desto höher ist das Isolationsniveau, wÀhrend eine engere Verwendung exklusiver Sperren die Geschwindigkeit erhöht.
Quelle: habr.com
