Transaktioner i InterSystems IRIS globals

Transaktioner i InterSystems IRIS globalsInterSystems IRIS DBMS stöder intressanta strukturer för att lagra data - globala. I huvudsak handlar det om flernivÄnycklar med diverse extra godsaker i form av transaktioner, snabba funktioner för att korsa datatrÀd, lÄs och ett eget ObjectScript-sprÄk.

LÀs mer om globaler i artikelserien "Globals Àr skattsvÀrd för att lagra data":

TrÀd. Del 1
TrÀd. Del 2
Glesa arrayer. Del 3

Jag blev intresserad av hur transaktioner implementeras i globala, vilka funktioner som finns. Detta Àr trots allt en helt annan struktur för att lagra data Àn de vanliga tabellerna. Mycket lÀgre nivÄ.

Som bekant frÄn teorin om relationsdatabaser mÄste en bra implementering av transaktioner uppfylla kraven SYRA:

A - Atomicitet (atomicitet). Alla Àndringar som görs i transaktionen eller inga alls registreras.

C - Konsistens. NÀr en transaktion har slutförts mÄste databasens logiska tillstÄnd vara internt konsekvent. PÄ mÄnga sÀtt berör detta krav programmeraren, men i fallet med SQL-databaser gÀller det Àven frÀmmande nycklar.

Jag - Isolera. Transaktioner som löper parallellt bör inte pÄverka varandra.

D - HÄllbar. Efter framgÄngsrikt slutförande av en transaktion bör problem pÄ lÀgre nivÄer (till exempel strömavbrott) inte pÄverka data som Àndras av transaktionen.

Globaler Àr icke-relationella datastrukturer. De designades för att köra supersnabbt pÄ mycket begrÀnsad hÄrdvara. LÄt oss titta pÄ implementeringen av transaktioner i globala med hjÀlp av officiella IRIS docker-bild.

För att stödja transaktioner i IRIS anvÀnds följande kommandon: TSTART, TCOMMIT, TROLLBACK.

1. Atomicitet

Det enklaste sÀttet att kontrollera Àr atomicitet. Vi kollar frÄn databaskonsolen.

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

Sedan avslutar vi:

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

Vi fÄr:

1 2 3

Allt Àr bra. Atomiciteten bibehÄlls: alla förÀndringar registreras.

LÄt oss komplicera uppgiften, introducera ett fel och se hur transaktionen sparas, delvis eller inte alls.

LÄt oss kontrollera atomiciteten igen:

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

Sedan kommer vi att tvÄngsstoppa containern, lansera den och se.

docker kill my-iris

Detta kommando motsvarar nÀstan en forcerad avstÀngning, eftersom det skickar en SIGKILL-signal för att stoppa processen omedelbart.

Kanske var transaktionen delvis sparad?

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

– Nej, det har inte överlevt.

LÄt oss prova ÄterstÀllningskommandot:

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)

Inget har överlevt heller.

2. Konsekvens

Eftersom i databaser baserade pÄ globaler görs nycklar Àven pÄ globaler (lÄt mig pÄminna om att en global Àr en struktur pÄ lÀgre nivÄ för att lagra data Àn en relationstabell), för att uppfylla konsistenskravet mÄste en förÀndring av nyckeln inkluderas i samma transaktion som en förÀndring i den globala.

Till exempel har vi en global ^person, dÀr vi lagrar personligheter och vi anvÀnder TIN som nyckel.

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

För att göra en snabb sökning pÄ efternamn och förnamn gjorde vi ^index-nyckeln.

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

För att databasen ska vara konsekvent mÄste vi lÀgga till personan sÄ hÀr:

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

Följaktligen, nÀr vi raderar mÄste vi ocksÄ anvÀnda en transaktion:

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

Att uppfylla konsistenskravet vilar med andra ord helt pÄ programmerarens axlar. Men nÀr det kommer till globala, Àr detta normalt, pÄ grund av deras lÄga natur.

3. Isolering

Det Àr hÀr vildmarken börjar. MÄnga anvÀndare arbetar samtidigt pÄ samma databas och Àndrar samma data.

Situationen Àr jÀmförbar med nÀr mÄnga anvÀndare samtidigt arbetar med samma kodlager och försöker göra Àndringar i mÄnga filer samtidigt.

Databasen ska reda ut allt i realtid. Med tanke pÄ att det i seriösa företag till och med finns en speciell person som Àr ansvarig för versionskontroll (för att slÄ samman filialer, lösa konflikter etc.), och databasen mÄste göra allt detta i realtid, uppgiftens komplexitet och riktigheten av databasdesign och kod som tjÀnar den.

Databasen kan inte förstÄ innebörden av de ÄtgÀrder som utförs av anvÀndare för att undvika konflikter om de arbetar med samma data. Det kan bara Ängra en transaktion som Àr i konflikt med en annan, eller utföra dem i följd.

Ett annat problem Àr att under utförandet av en transaktion (före en commit) kan databasens tillstÄnd vara inkonsekvent, sÄ det Àr önskvÀrt att andra transaktioner inte har tillgÄng till databasens inkonsekventa tillstÄnd, vilket uppnÄs i relationsdatabaser pÄ mÄnga sÀtt: skapa ögonblicksbilder, multiversionsrader och etc.

NÀr man utför transaktioner parallellt Àr det viktigt för oss att de inte stör varandra. Detta Àr egenskapen för isolering.

SQL definierar 4 isoleringsnivÄer:

  • LÄS OBEGRÄNSAD
  • LÄS ÅTAGANDE
  • REPETERBAR LÄSNING
  • SERIALISERBAR

LÄt oss titta pÄ varje nivÄ separat. Kostnaderna för att implementera varje nivÄ vÀxer nÀstan exponentiellt.

LÄS OBEGRÄNSAD - detta Ă€r den lĂ€gsta nivĂ„n av isolering, men samtidigt den snabbaste. Transaktioner kan lĂ€sa Ă€ndringar som gjorts av varandra.

LÄS ÅTAGANDE Ă€r nĂ€sta nivĂ„ av isolering, vilket Ă€r en kompromiss. Transaktioner kan inte lĂ€sa varandras Ă€ndringar före commit, men de kan lĂ€sa eventuella Ă€ndringar som gjorts efter commit.

Om vi ​​har en lĂ„ng transaktion T1, under vilken commits Ă€gde rum i transaktionerna T2, T3 ... Tn, som fungerade med samma data som T1, sĂ„ fĂ„r vi ett annat resultat varje gĂ„ng nĂ€r vi begĂ€r data i T1. Detta fenomen kallas icke-repeterbar lĂ€sning.

REPETERBAR LÄSNING — i denna isoleringsnivĂ„ har vi inte fenomenet icke-repeterbar lĂ€sning, pĂ„ grund av det faktum att för varje begĂ€ran att lĂ€sa data skapas en ögonblicksbild av resultatdata och nĂ€r de Ă„teranvĂ€nds i samma transaktion, data frĂ„n ögonblicksbilden Ă€r anvĂ€nd. Det Ă€r dock möjligt att lĂ€sa fantomdata pĂ„ denna isoleringsnivĂ„. Detta syftar pĂ„ att lĂ€sa nya rader som har lagts till av parallella genomförda transaktioner.

SERIALISERBAR — högsta isoleringsnivĂ„n. Det kĂ€nnetecknas av det faktum att data som anvĂ€nds pĂ„ nĂ„got sĂ€tt i en transaktion (lĂ€sning eller Ă€ndring) blir tillgĂ€nglig för andra transaktioner först efter att den första transaktionen har slutförts.

LÄt oss först ta reda pÄ om det finns isolering av operationer i en transaktion frÄn huvudtrÄden. LÄt oss öppna 2 terminalfönster.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Det finns ingen isolering. En trÄd ser vad den andra som öppnade transaktionen gör.

LÄt oss se om transaktioner av olika trÄdar ser vad som hÀnder inuti dem.

LÄt oss öppna 2 terminalfönster och öppna 2 transaktioner parallellt.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Parallella transaktioner ser varandras data. SĂ„, vi fick den enklaste, men ocksĂ„ den snabbaste isoleringsnivĂ„n, LÄS OCMITED.

I princip kan detta förvÀntas för globala, dÀr prestation alltid har varit en prioritet.

Vad hÀnder om vi behöver en högre grad av isolering i operationer pÄ globala enheter?

HÀr behöver du fundera pÄ varför isoleringsnivÄer överhuvudtaget behövs och hur de fungerar.

Den högsta isoleringsnivÄn, SERIALIZE, innebÀr att resultatet av transaktioner som utförs parallellt Àr likvÀrdigt med deras sekventiella exekvering, vilket garanterar frÄnvaron av kollisioner.

Vi kan göra detta med smarta lĂ„s i ObjectScript, som har mĂ„nga olika anvĂ€ndningsomrĂ„den: du kan göra regelbunden, inkrementell, multipla lĂ„sning med kommandot LÅS.

LÀgre isoleringsnivÄer Àr avvÀgningar utformade för att öka databashastigheten.

LÄt oss se hur vi kan uppnÄ olika nivÄer av isolering med hjÀlp av lÄs.

Denna operatör lÄter dig ta inte bara exklusiva lÄs som behövs för att Àndra data, utan sÄ kallade delade lÄs, som kan ta flera trÄdar parallellt nÀr de behöver lÀsa data som inte ska Àndras av andra processer under lÀsningsprocessen.

Mer information om tvÄfasblockeringsmetoden pÄ ryska och engelska:

→ TvĂ„fas blockering
→ TvĂ„faslĂ„sning

SvÄrigheten Àr att under en transaktion kan tillstÄndet för databasen vara inkonsekvent, men dessa inkonsekventa data Àr synliga för andra processer. Hur undviker man detta?

Med hjÀlp av lÄs kommer vi att skapa synlighetsfönster dÀr tillstÄndet för databasen kommer att vara konsekvent. Och all tillgÄng till sÄdana synlighetsfönster för den överenskomna staten kommer att kontrolleras av lÄs.

Delade lĂ„s pĂ„ samma data Ă€r Ă„teranvĂ€ndbara – flera processer kan ta dem. Dessa lĂ„s hindrar andra processer frĂ„n att Ă€ndra data, d.v.s. de anvĂ€nds för att bilda fönster med konsekvent databastillstĂ„nd.

Exklusiva lÄs anvÀnds för dataÀndringar - endast en process kan ta ett sÄdant lÄs. Ett exklusivt lÄs kan tas av:

  1. Alla processer om uppgifterna Àr gratis
  2. Endast processen som har ett delat lÄs pÄ denna data och var den första att begÀra ett exklusivt lÄs.

Transaktioner i InterSystems IRIS globals

Ju smalare synlighetsfönstret Àr, desto lÀngre tid mÄste andra processer vÀnta pÄ det, men desto mer konsekvent kan tillstÄndet för databasen i den vara.

READ_COMMITTED — KĂ€rnan i denna nivĂ„ Ă€r att vi bara ser engagerad data frĂ„n andra trĂ„dar. Om uppgifterna i en annan transaktion Ă€nnu inte har begĂ„tts, ser vi dess gamla version.

Detta gör att vi kan parallellisera arbetet istÀllet för att vÀnta pÄ att lÄset ska slÀppas.

Utan speciella knep kommer vi inte att kunna se den gamla versionen av datan i IRIS, sÄ vi fÄr nöja oss med lÄs.

Följaktligen kommer vi att behöva anvÀnda delade lÄs för att tillÄta att data endast kan lÀsas i ögonblick av konsistens.

LÄt oss sÀga att vi har en anvÀndarbas ^person som överför pengar till varandra.

Överföringsögonblick frĂ„n person 123 till 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)

Ögonblicket för att begĂ€ra summan pengar frĂ„n person 123 innan debitering mĂ„ste Ă„tföljas av en exklusiv spĂ€rr (som standard):

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

Och om du behöver visa kontostatusen i ditt personliga konto kan du anvÀnda ett delat lÄs eller inte anvÀnda det alls:

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

Men om vi antar att databasoperationer utförs nÀstan omedelbart (lÄt mig pÄminna dig om att globaler Àr en struktur pÄ mycket lÀgre nivÄ Àn en relationstabell), sÄ minskar behovet av denna nivÄ.

REPETERBAR LÄSNING - Denna isoleringsnivĂ„ möjliggör flera lĂ€sningar av data som kan modifieras av samtidiga transaktioner.

Följaktligen mÄste vi sÀtta ett delat lÄs för att lÀsa data som vi Àndrar och exklusiva lÄs pÄ data som vi Àndrar.

Lyckligtvis lÄter LOCK-operatören dig i detalj lista alla nödvÀndiga lÄs, som det kan finnas mÄnga av, i ett uttalande.

LOCK +^person(123, amount)#”S”
Ń‡Ń‚Đ”ĐœĐžĐ” ^person(123, amount)

andra operationer (för nÀrvarande försöker parallella trÄdar Àndra ^person(123, mÀngd), men kan inte)

LOCK +^person(123, amount)
ĐžĐ·ĐŒĐ”ĐœĐ”ĐœĐžĐ” ^person(123, amount)
LOCK -^person(123, amount)

Ń‡Ń‚Đ”ĐœĐžĐ” ^person(123, amount)
LOCK -^person(123, amount)#”S”

NÀr du listar lÄs separerade med kommatecken tas de sekventiellt, men om du gör sÄ hÀr:

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

sedan tas de atomÀrt pÄ en gÄng.

GE SOM SERIE — vi mĂ„ste sĂ€tta lĂ„s sĂ„ att alla transaktioner som har gemensamma data i slutĂ€ndan exekveras sekventiellt. För detta tillvĂ€gagĂ„ngssĂ€tt bör de flesta lĂ„s vara exklusiva och anvĂ€ndas pĂ„ de minsta omrĂ„dena i vĂ€rlden för prestanda.

Om vi ​​pratar om att debitera medel i den globala ^personen, Ă€r endast SERIALISE-isoleringsnivĂ„n acceptabel för det, eftersom pengar mĂ„ste spenderas strikt sekventiellt, annars Ă€r det möjligt att spendera samma summa flera gĂ„nger.

4. HÄllbarhet

Jag genomförde tester med hÄrd skÀrning av behÄllaren med hjÀlp av

docker kill my-iris

Basen tolererade dem vÀl. Inga problem identifierades.

Slutsats

För globala företag har InterSystems IRIS transaktionsstöd. De Àr verkligen atomÀra och pÄlitliga. För att sÀkerstÀlla konsistens i en databas baserad pÄ globaler krÀvs programmeringsinsatser och anvÀndning av transaktioner, eftersom den inte har komplexa inbyggda konstruktioner sÄsom frÀmmande nycklar.

IsoleringsnivĂ„n för globala enheter utan att anvĂ€nda lĂ„s Ă€r LÄS OCMITED, och nĂ€r du anvĂ€nder lĂ„s kan det sĂ€kerstĂ€llas upp till SERIALISERA-nivĂ„n.

Korrektheten och hastigheten för transaktioner pÄ globala enheter beror mycket pÄ programmerarens skicklighet: ju mer delade lÄs anvÀnds vid lÀsning, desto högre isoleringsnivÄ och ju mer snÀvt exklusiva lÄs tas, desto snabbare prestanda.

KĂ€lla: will.com

LĂ€gg en kommentar