Transaccions a InterSystems IRIS globals

Transaccions a InterSystems IRIS globalsEl SGBD IRIS d'InterSystems admet estructures interessants per emmagatzemar dades: globals. Essencialment, es tracta de claus de diversos nivells amb diverses avantatges addicionals en forma de transaccions, funcions ràpides per recórrer arbres de dades, panys i el seu propi llenguatge ObjectScript.

Llegiu més sobre els globals a la sèrie d'articles "Els globals són espases de tresor per emmagatzemar dades":

Arbres. Part 1
Arbres. Part 2
Matrius dispersos. Part 3

Em vaig interessar en com s'implementen les transaccions als globals, quines característiques hi ha. Després de tot, aquesta és una estructura completament diferent per emmagatzemar dades que les taules habituals. Nivell molt inferior.

Com és sabut des de la teoria de les bases de dades relacionals, una bona implementació de les transaccions ha de satisfer els requisits ÀCID:

A - Atòmica (atomicitat). Es registren tots els canvis fets en la transacció o cap.

C - Coherència. Un cop finalitzada una transacció, l'estat lògic de la base de dades ha de ser coherent internament. En molts aspectes, aquest requisit concerneix al programador, però en el cas de les bases de dades SQL també es refereix a les claus externes.

I - Aïllar. Les transaccions que s'executen en paral·lel no s'han d'afectar mútuament.

D - Durable. Després de completar correctament una transacció, els problemes a nivells inferiors (per exemple, una fallada d'alimentació) no haurien d'afectar les dades modificades per la transacció.

Els globals són estructures de dades no relacionals. Estan dissenyats per funcionar molt ràpid amb un maquinari molt limitat. Vegem la implementació de transaccions en globals utilitzant imatge oficial del docker IRIS.

Per donar suport a les transaccions a IRIS, s'utilitzen les ordres següents: COMENÇA, TCOMMIT, TROLLBACK.

1. Atomicitat

La forma més fàcil de comprovar és l'atomicitat. Comprovem des de la consola de la base de dades.

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

Aleshores concloem:

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

Obtenim:

1 2 3

Tot està bé. Es manté l'atomicitat: es registren tots els canvis.

Complicarem la tasca, introduïm un error i veiem com es guarda la transacció, parcialment o gens.

Tornem a comprovar l'atomicitat:

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

Aleshores aturarem amb força el contenidor, el llançarem i veurem.

docker kill my-iris

Aquesta ordre és gairebé equivalent a un apagat forçat, ja que envia un senyal SIGKILL per aturar el procés immediatament.

Potser la transacció s'ha desat parcialment?

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

- No, no ha sobreviscut.

Provem l'ordre de rollback:

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)

Tampoc no ha sobreviscut res.

2. Coherència

Com que a les bases de dades basades en globals, les claus també es fan amb globals (permeteu-me recordar que un global és una estructura de nivell inferior per emmagatzemar dades que una taula relacional), per complir el requisit de coherència, s'ha d'incloure un canvi de clau. en la mateixa transacció que un canvi en el global.

Per exemple, tenim una ^persona global, en la qual emmagatzemem personalitats i fem servir el TIN com a clau.

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

Per poder fer una cerca ràpida per cognom i nom, hem creat la clau ^index.

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

Perquè la base de dades sigui coherent, hem d'afegir la persona com aquesta:

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

En conseqüència, en suprimir també hem d'utilitzar una transacció:

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

En altres paraules, el compliment del requisit de coherència depèn totalment del programador. Però quan es tracta de globals, això és normal, a causa del seu caràcter de baix nivell.

3. Aïllament

Aquí és on comencen els salvatges. Molts usuaris treballen simultàniament a la mateixa base de dades, canviant les mateixes dades.

La situació és comparable a quan molts usuaris treballen simultàniament amb el mateix dipòsit de codi i intenten fer canvis simultàniament en molts fitxers alhora.

La base de dades hauria d'ordenar-ho tot en temps real. Tenint en compte que a les empreses serioses fins i tot hi ha una persona especial que s'encarrega del control de versions (per fusionar sucursals, resoldre conflictes, etc.), i la base de dades ha de fer tot això en temps real, la complexitat de la tasca i la correcció de la disseny de base de dades i codi que li serveix.

La base de dades no pot entendre el significat de les accions realitzades pels usuaris per tal d'evitar conflictes si estan treballant amb les mateixes dades. Només pot desfer una transacció que entra en conflicte amb una altra o executar-les de manera seqüencial.

Un altre problema és que durant l'execució d'una transacció (abans d'una confirmació), l'estat de la base de dades pot ser inconsistent, per la qual cosa és desitjable que altres transaccions no tinguin accés a l'estat inconsistent de la base de dades, que s'aconsegueix en les bases de dades relacionals. de moltes maneres: creant instantànies, files amb versions múltiples, etc.

Quan executem transaccions en paral·lel, és important per a nosaltres que no interfereixin entre si. Aquesta és la propietat de l'aïllament.

SQL defineix 4 nivells d'aïllament:

  • LLEGIR SENSE COMPROMÍS
  • LLEGIR COMPROMESSOS
  • LECTURA REPETIBLE
  • SERIALISABLE

Mirem cada nivell per separat. Els costos d'implementació de cada nivell creixen gairebé exponencialment.

LLEGIR SENSE COMPROMÍS - aquest és el nivell d'aïllament més baix, però alhora el més ràpid. Les transaccions poden llegir els canvis fets entre si.

LLEGIR COMPROMESSOS és el següent nivell d'aïllament, que és un compromís. Les transaccions no poden llegir els canvis de les altres abans de la confirmació, però poden llegir els canvis fets després de la confirmació.

Si tenim una transacció llarga T1, durant la qual es van realitzar commits a les transaccions T2, T3 ... Tn, que funcionava amb les mateixes dades que T1, aleshores en demanar dades a T1 obtindrem un resultat diferent cada vegada. Aquest fenomen s'anomena lectura no repetible.

LECTURA REPETIBLE — en aquest nivell d'aïllament no tenim el fenomen de lectura no repetible, a causa del fet que per a cada sol·licitud de lectura de dades es crea una instantània de les dades del resultat i quan es reutilitza en la mateixa transacció, les dades de la instantània s'utilitza. Tanmateix, és possible llegir dades fantasma en aquest nivell d'aïllament. Això fa referència a la lectura de files noves que es van afegir per transaccions compromeses paral·leles.

SERIALISABLE - El nivell més alt d'aïllament. Es caracteritza pel fet que les dades utilitzades de qualsevol manera en una transacció (llegida o canviant) només estan disponibles per a altres transaccions després de la finalització de la primera transacció.

Primer, esbrineu si hi ha aïllament d'operacions en una transacció del fil principal. Obrim 2 finestres de terminal.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

No hi ha aïllament. Un fil veu què està fent el segon que va obrir la transacció.

A veure si les transaccions de diferents fils veuen què passa dins d'ells.

Obrim 2 finestres de terminal i obrim 2 transaccions en paral·lel.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Les transaccions paral·leles veuen les dades de l'altre. Així doncs, tenim el nivell d'aïllament més senzill, però també el més ràpid, LLEGIR SIN COMPROMÍS.

En principi, això es podria esperar per als globals, per als quals el rendiment sempre ha estat una prioritat.

Què passa si necessitem un nivell més alt d'aïllament en les operacions a nivell global?

Aquí heu de pensar per què es necessiten nivells d'aïllament i com funcionen.

El nivell d'aïllament més alt, SERIALIZE, significa que el resultat de les transaccions executades en paral·lel és equivalent a la seva execució seqüencial, la qual cosa garanteix l'absència de col·lisions.

Ho podem fer utilitzant bloquejos intel·ligents a ObjectScript, que tenen molts usos diferents: podeu fer un bloqueig regular, incremental i múltiple amb l'ordre LOCK.

Els nivells d'aïllament més baixos són compensacions dissenyades per augmentar la velocitat de la base de dades.

Vegem com podem aconseguir diferents nivells d'aïllament mitjançant panys.

Aquest operador permet prendre no només els bloquejos exclusius necessaris per canviar les dades, sinó també els anomenats bloquejos compartits, que poden agafar diversos fils en paral·lel quan necessiten llegir dades que no haurien de ser modificades per altres processos durant el procés de lectura.

Més informació sobre el mètode de bloqueig en dues fases en rus i anglès:

Bloqueig bifàsic
Bloqueig bifàsic

La dificultat és que durant una transacció l'estat de la base de dades pot ser inconsistent, però aquestes dades inconsistents són visibles per a altres processos. Com evitar això?

Utilitzant bloquejos, crearem finestres de visibilitat en les quals l'estat de la base de dades serà coherent. I tot l'accés a aquestes finestres de visibilitat de l'estat pactat estarà controlat per panys.

Els bloquejos compartits a les mateixes dades es poden reutilitzar; diversos processos els poden prendre. Aquests bloquejos impedeixen que altres processos canviïn les dades, és a dir. s'utilitzen per formar finestres d'estat de base de dades coherent.

Els bloquejos exclusius s'utilitzen per als canvis de dades: només un procés pot prendre aquest bloqueig. Un bloqueig exclusiu es pot prendre per:

  1. Qualsevol procés si les dades són gratuïtes
  2. Només el procés que té un bloqueig compartit sobre aquestes dades i va ser el primer a sol·licitar un bloqueig exclusiu.

Transaccions a InterSystems IRIS globals

Com més estreta sigui la finestra de visibilitat, més temps hauran d'esperar els altres processos, però més coherent pot ser l'estat de la base de dades dins d'ella.

READ_COMMITTED — l'essència d'aquest nivell és que només veiem dades compromeses d'altres fils. Si les dades d'una altra transacció encara no s'han compromès, veurem la seva versió antiga.

Això ens permet paral·lelitzar l'obra en lloc d'esperar que s'alliberi el pany.

Sense trucs especials, no podrem veure la versió antiga de les dades a IRIS, així que haurem de conformar-nos amb els panys.

En conseqüència, haurem d'utilitzar bloquejos compartits per permetre que les dades només es puguin llegir en moments de coherència.

Suposem que tenim una base d'usuaris ^persona que es transfereixen diners entre elles.

Moment del trasllat de la persona 123 a la persona 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)

El moment de sol·licitar l'import de diners a la persona 123 abans de domiciliar ha d'anar acompanyat d'un bloqueig exclusiu (per defecte):

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

I si necessiteu mostrar l'estat del compte al vostre compte personal, podeu utilitzar un bloqueig compartit o no utilitzar-lo en absolut:

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

Tanmateix, si suposem que les operacions de la base de dades es realitzen gairebé a l'instant (permeteu-me recordar que els globals són una estructura de nivell molt inferior que una taula relacional), aleshores la necessitat d'aquest nivell disminueix.

LECTURA REPETIBLE - Aquest nivell d'aïllament permet múltiples lectures de dades que es poden modificar per transaccions concurrents.

En conseqüència, haurem de posar un bloqueig compartit a la lectura de les dades que canviem i bloquejos exclusius a les dades que canviem.

Afortunadament, l'operador LOCK us permet enumerar detalladament tots els bloquejos necessaris, dels quals n'hi pot haver molts, en una sola declaració.

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

altres operacions (en aquest moment els fils paral·lels intenten canviar ^person(123, import), però no poden)

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

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

Quan s'enumeren els bloquejos separats per comes, es prenen seqüencialment, però si feu això:

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

llavors es prenen atòmicament tots alhora.

SERIALITZAR — haurem d'establir bloquejos perquè finalment totes les transaccions que tinguin dades comunes s'executin de manera seqüencial. Per a aquest enfocament, la majoria dels bloquejos haurien de ser exclusius i s'haurien d'aplicar a les zones més petites del món per obtenir un rendiment.

Si parlem de càrrec de fons a la ^persona global, només és acceptable el nivell d'aïllament SERIALIZE, ja que els diners s'han de gastar de manera estrictament seqüencial, en cas contrari, és possible gastar la mateixa quantitat diverses vegades.

4. Durabilitat

Vaig realitzar proves amb tall dur del contenidor utilitzant

docker kill my-iris

La base els tolerava bé. No s'han identificat problemes.

Conclusió

Per als globals, InterSystems IRIS té suport de transaccions. Són realment atòmics i fiables. Per garantir la coherència d'una base de dades basada en globals, es requereixen els esforços del programador i l'ús de transaccions, ja que no té construccions incorporades complexes com ara claus forasteres.

El nivell d'aïllament dels globals sense utilitzar bloquejos és READ UNCOMMITED, i quan s'utilitzen bloquejos es pot assegurar fins al nivell SERIALIZE.

La correcció i la velocitat de les transaccions en globals depèn molt de l'habilitat del programador: com més s'utilitzen bloquejos compartits durant la lectura, més alt és el nivell d'aïllament i com més estretament exclusius es prenguin, més ràpid serà el rendiment.

Font: www.habr.com

Afegeix comentari