Transaktioner i InterSystems IRIS globals

Transaktioner i InterSystems IRIS globalsInterSystems IRIS DBMS understøtter interessante strukturer til lagring af data - globale. I bund og grund er disse nøgler på flere niveauer med forskellige ekstra godbidder i form af transaktioner, hurtige funktioner til at krydse datatræer, låse og sit eget ObjectScript-sprog.

Læs mere om globaler i serien af ​​artikler "Globals er skattesværd til lagring af data":

Træer. Del 1
Træer. Del 2
Sparsomme arrays. Del 3

Jeg blev interesseret i, hvordan transaktioner implementeres i globalt, hvilke funktioner der er. Det er jo en helt anden struktur til lagring af data end de sædvanlige tabeller. Meget lavere niveau.

Som det er kendt fra teorien om relationsdatabaser, skal en god implementering af transaktioner opfylde kravene ACID:

A - Atomicitet (atomicitet). Alle ændringer foretaget i transaktionen eller slet ingen bliver registreret.

C - Konsistens. Når en transaktion er gennemført, skal databasens logiske tilstand være internt konsistent. På mange måder vedrører dette krav programmøren, men i forbindelse med SQL-databaser vedrører det også fremmednøgler.

I - Isoler. Transaktioner, der kører parallelt, bør ikke påvirke hinanden.

D - Holdbar. Efter vellykket gennemførelse af en transaktion bør problemer på lavere niveauer (f.eks. strømsvigt) ikke påvirke de data, der ændres af transaktionen.

Globaler er ikke-relationelle datastrukturer. De er designet til at køre superhurtigt på meget begrænset hardware. Lad os se på implementeringen af ​​transaktioner i globaler ved hjælp af officielle IRIS docker-billede.

For at understøtte transaktioner i IRIS bruges følgende kommandoer: TSTART, TCOMMIT, TROLLBACK.

1. Atomicitet

Den nemmeste måde at kontrollere er atomicitet. Vi tjekker fra databasekonsollen.

Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMIT

Så konkluderer vi:

Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)

Vi får:

1 2 3

Alt er fint. Atomiciteten bibeholdes: alle ændringer registreres.

Lad os komplicere opgaven, introducere en fejl og se, hvordan transaktionen gemmes, delvist eller slet ikke.

Lad os tjekke atomiciteten igen:

Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3

Så vil vi med magt stoppe containeren, lancere den og se.

docker kill my-iris

Denne kommando svarer næsten til en kraftnedlukning, da den sender et SIGKILL-signal for at stoppe processen med det samme.

Måske blev transaktionen delvist gemt?

WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)

- Nej, den har ikke overlevet.

Lad os prøve rollback-kommandoen:

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)

Intet har heller overlevet.

2. Konsistens

Da der i databaser baseret på globaler også laves nøgler på globaler (lad mig minde om, at en global er en struktur på lavere niveau til lagring af data end en relationstabel), for at opfylde konsistenskravet, skal en ændring i nøglen inkluderes i samme transaktion som en ændring i det globale.

For eksempel har vi en global ^person, hvor vi gemmer personligheder, og vi bruger TIN som en nøgle.

^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...

For at få en hurtig søgning på efternavn og fornavn lavede vi ^index-tasten.

^index(‘Kamenev’, ‘Sergey’, 1234567) = 1

For at databasen skal være konsistent, skal vi tilføje personaen sådan:

TSTART
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
TCOMMIT

Derfor skal vi også bruge en transaktion ved sletning:

TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMIT

Opfyldelse af konsistenskravet hviler med andre ord helt på programmørens skuldre. Men når det kommer til globaler, er dette normalt på grund af deres lavtliggende natur.

3. Isolation

Det er her, vildmarken begynder. Mange brugere arbejder samtidigt på den samme database og ændrer de samme data.

Situationen kan sammenlignes med, når mange brugere samtidigt arbejder med det samme kodelager og forsøger at foretage ændringer til mange filer på én gang.

Databasen skal sortere det hele i realtid. I betragtning af, at der i seriøse virksomheder endda er en særlig person, der er ansvarlig for versionskontrol (for sammenlægning af filialer, løsning af konflikter osv.), og databasen skal gøre alt dette i realtid, kompleksiteten af ​​opgaven og rigtigheden af databasedesign og kode, der tjener det.

Databasen kan ikke forstå betydningen af ​​de handlinger, som brugerne udfører for at undgå konflikter, hvis de arbejder på de samme data. Den kan kun fortryde én transaktion, der er i konflikt med en anden, eller udføre dem sekventielt.

Et andet problem er, at under udførelsen af ​​en transaktion (før en commit) kan databasens tilstand være inkonsistent, så det er ønskeligt, at andre transaktioner ikke har adgang til databasens inkonsistente tilstand, som opnås i relationelle databaser på mange måder: oprettelse af snapshots, multiversionsrækker osv.

Når man udfører transaktioner parallelt, er det vigtigt for os, at de ikke forstyrrer hinanden. Dette er egenskaben ved isolation.

SQL definerer 4 isolationsniveauer:

  • LÆS UNCOMMITTED
  • LÆS FORPLIGTET
  • GENTAGLIG LÆSNING
  • SERIALISERbar

Lad os se på hvert niveau separat. Omkostningerne ved at implementere hvert niveau vokser næsten eksponentielt.

LÆS UNCOMMITTED - dette er det laveste niveau af isolation, men samtidig det hurtigste. Transaktioner kan læse ændringer foretaget af hinanden.

LÆS FORPLIGTET er det næste niveau af isolation, som er et kompromis. Transaktioner kan ikke læse hinandens ændringer før commit, men de kan læse eventuelle ændringer foretaget efter commit.

Hvis vi har en lang transaktion T1, hvor commits fandt sted i transaktioner T2, T3 ... Tn, som fungerede med de samme data som T1, så vil vi, når vi anmoder om data i T1, få et andet resultat hver gang. Dette fænomen kaldes ikke-gentagelig læsning.

GENTAGLIG LÆSNING — på dette isolationsniveau har vi ikke fænomenet med ikke-gentagelig læsning, på grund af det faktum, at der for hver anmodning om at læse data oprettes et øjebliksbillede af resultatdataene, og når de genbruges i samme transaktion, dataene fra snapshottet anvendes. Det er dog muligt at læse fantomdata på dette isolationsniveau. Dette refererer til at læse nye rækker, der blev tilføjet af parallelle forpligtede transaktioner.

SERIALISERbar — det højeste isoleringsniveau. Det er kendetegnet ved, at data, der bruges på nogen måde i en transaktion (læsning eller ændring), først bliver tilgængelige for andre transaktioner efter afslutningen af ​​den første transaktion.

Lad os først finde ud af, om der er isolering af operationer i en transaktion fra hovedtråden. Lad os åbne 2 terminalvinduer.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Der er ingen isolation. Den ene tråd ser, hvad den anden, der åbnede transaktionen, laver.

Lad os se, om transaktioner af forskellige tråde kan se, hvad der sker inde i dem.

Lad os åbne 2 terminalvinduer og åbne 2 transaktioner parallelt.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Parallelle transaktioner ser hinandens data. Så vi fik det enkleste, men også det hurtigste isolationsniveau, LÆS UENGAGELIGT.

I princippet kunne dette forventes for globale, hvor præstation altid har været en prioritet.

Hvad hvis vi har brug for et højere niveau af isolation i operationer på globalt?

Her skal du tænke over, hvorfor der overhovedet er brug for isolationsniveauer, og hvordan de fungerer.

Det højeste isolationsniveau, SERIALIZE, betyder, at resultatet af transaktioner, der udføres parallelt, svarer til deres sekventielle eksekvering, hvilket garanterer fravær af kollisioner.

Vi kan gøre dette ved hjælp af smarte låse i ObjectScript, som har mange forskellige anvendelser: du kan lave regelmæssig, trinvis, multiple låsning med kommandoen LOCK.

Lavere isolationsniveauer er afvejninger designet til at øge databasehastigheden.

Lad os se, hvordan vi kan opnå forskellige niveauer af isolation ved hjælp af låse.

Denne operatør giver dig mulighed for at tage ikke kun eksklusive låse, der er nødvendige for at ændre data, men såkaldte shared locks, som kan tage flere tråde parallelt, når de skal læse data, som ikke bør ændres af andre processer under læseprocessen.

Mere information om den tofasede blokeringsmetode på russisk og engelsk:

To-faset blokering
To-faset låsning

Problemet er, at under en transaktion kan databasens tilstand være inkonsistent, men disse inkonsistente data er synlige for andre processer. Hvordan undgår man dette?

Ved hjælp af låse vil vi oprette synlighedsvinduer, hvor databasens tilstand vil være konsistent. Og al adgang til sådanne vinduer af synlighed af den aftalte tilstand vil blive kontrolleret af låse.

Delte låse på de samme data kan genbruges – flere processer kan klare dem. Disse låse forhindrer andre processer i at ændre data, dvs. de bruges til at danne vinduer med ensartet databasetilstand.

Eksklusive låse bruges til dataændringer - kun én proces kan tage en sådan lås. En eksklusiv lås kan tages af:

  1. Enhver proces, hvis dataene er gratis
  2. Kun den proces, der har en delt lås på disse data og var den første til at anmode om en eksklusiv lås.

Transaktioner i InterSystems IRIS globals

Jo snævrere synlighedsvinduet er, jo længere tid skal andre processer vente på det, men jo mere konsistent kan databasens tilstand i den være.

READ_COMMITTED — essensen af ​​dette niveau er, at vi kun ser forpligtede data fra andre tråde. Hvis dataene i en anden transaktion endnu ikke er blevet forpligtet, så ser vi dens gamle version.

Dette giver os mulighed for at parallelisere arbejdet i stedet for at vente på, at låsen udløses.

Uden specielle tricks vil vi ikke kunne se den gamle version af dataene i IRIS, så vi må nøjes med låse.

Derfor bliver vi nødt til at bruge delte låse for kun at tillade data at blive læst i øjeblikke med konsistens.

Lad os sige, at vi har en brugerbase ^person, der overfører penge til hinanden.

Overførselstidspunkt fra person 123 til 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)

Øjeblikket for anmodning om pengebeløbet fra person 123 før debitering skal ledsages af en eksklusiv blokering (som standard):

LOCK +^person(123)
Write ^person(123)

Og hvis du har brug for at vise kontostatus på din personlige konto, så kan du bruge en delt lås eller slet ikke bruge den:

LOCK +^person(123)#”S”
Write ^person(123)

Men hvis vi antager, at databaseoperationer udføres næsten øjeblikkeligt (lad mig minde dig om, at globaler er en struktur på meget lavere niveau end en relationstabel), så falder behovet for dette niveau.

GENTAGLIG LÆSNING - Dette isolationsniveau giver mulighed for flere læsninger af data, der kan ændres ved samtidige transaktioner.

Derfor bliver vi nødt til at sætte en delt lås på at læse de data, vi ændrer, og eksklusive låse på de data, vi ændrer.

Heldigvis giver LOCK-operatøren dig mulighed for i detaljer at liste alle de nødvendige låse, som der kan være mange af, i én opgørelse.

LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)

andre operationer (på nuværende tidspunkt prøver parallelle tråde at ændre ^person(123, mængde), men kan ikke)

LOCK +^person(123, amount)
изменение ^person(123, amount)
LOCK -^person(123, amount)

чтение ^person(123, amount)
LOCK -^person(123, amount)#”S”

Når du angiver låse adskilt af kommaer, tages de sekventielt, men hvis du gør dette:

LOCK +(^person(123),^person(242))

så tages de atomært på én gang.

serialiserer — vi bliver nødt til at sætte låse, så i sidste ende alle transaktioner, der har fælles data, udføres sekventielt. Til denne tilgang bør de fleste låse være eksklusive og tages på de mindste områder i verden for ydeevne.

Hvis vi taler om debitering af midler i den globale ^person, så er det kun SERIALIZE-isolationsniveauet, der er acceptabelt for det, da penge skal bruges strengt sekventielt, ellers er det muligt at bruge det samme beløb flere gange.

4. Holdbarhed

Jeg gennemførte test med hård skæring af beholderen vha

docker kill my-iris

Basen tolererede dem godt. Ingen problemer blev identificeret.

Konklusion

For globale virksomheder har InterSystems IRIS transaktionssupport. De er virkelig atomare og pålidelige. For at sikre ensartethed i en database baseret på globaler kræves programmørindsats og brug af transaktioner, da den ikke har komplekse indbyggede konstruktioner såsom fremmednøgler.

Isolationsniveauet for globaler uden brug af låse er LÆS UNCOMMITED, og ​​ved brug af låse kan det sikres op til SERIALISER-niveauet.

Korrektheden og hastigheden af ​​transaktioner på globalt afhænger i høj grad af programmørens færdigheder: jo mere udbredt delte låse, der bruges ved læsning, jo højere isolationsniveau, og jo mere snævert eksklusive låse, desto hurtigere ydeevne.

Kilde: www.habr.com

Tilføj en kommentar