Az InterSystems IRIS DBMS olyan érdekes struktúrákat támogat az adatok tárolására, mint a globálisok. Ezek lényegében többszintű kulcsok különféle extra funkciókkal, mint a tranzakciók, gyors funkciók az adatfák bejárásához, zárolások és saját ObjectScript nyelv.
További információ a globálisokról a "Globálisok — az adatok tárolásának varázspálcái" című cikksorozatban:
Érdekesnek találom, hogy a globálisokban hogyan valósulnak meg a tranzakciók, és milyen sajátos jellemzőik vannak. Ez teljesen eltér a megszokott táblázatos adatszerkezettől. Sokkal alacsonyabb szintű.
A relális adatbázisok elméletéből jól ismert, hogy egy jó tranzakciós megvalósításnak meg kell felelnie a következő követelményeknek: :
A — Atomic (atomosság). Minden tranzakcióval végrehajtott változtatásnak rögzítve kell lennie, vagy egyáltalán semminek.
C — Consistency (konzisztencia). A tranzakció befejezése után az adatbázis logikai állapotának belsőleg ellentmondás nélkül kell lennie. Ez a követelmény nagyban a programozót érinti, de SQL adatbázisok esetén a külső kulcsokra is vonatkozik.
I — Isolate (izoláció). A párhuzamosan végrehajtott tranzakcióknak nem szabad hatással lenniük egymásra.
D — Durable (tartósság). A tranzakció sikeres befejezése után az alsóbb szintű problémáknak (például áramkimaradás) nem szabad hatással lenniük a tranzakció által módosított adatokra.
A globálisok nem relációs adatstruktúrák. Őket ultraváltozatos teljesítményű, nagyon korlátozott hardveren való munkára tervezték. Nézzük meg a tranzakciók megvalósítását a globálisokban az .
A tranzakciók támogatásához IRIS-ben a következő parancsokat használják: , , .
1. Atomosság
Az atomosság legkönnyebben tesztelhető. Ellenőrizzük az adatbázis konzoljából.
Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMITAztán kiadunk:
Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)Ez a következőket adja:
1 2 3Minden rendben. Az atomosság biztosítva van: minden változtatás rögzítve lett.
Nehézzé tesszük a feladatot, hibát vezetünk be, és megnézzük, hogyan marad a tranzakció, részben vagy egyáltalán nem.
Újra ellenőrizzük az atomosságot:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3Aztán kényszerítve leállítjuk a konténert, újraindítjuk, és megnézzük.
docker kill my-irisEz a parancs gyakorlatilag egyenértékű az áramellátás kényszerített megszakításával, mivel azonnali leállító jelet (SIGKILL) küld.
Lehet, hogy a tranzakció részlegesen megmaradt?
WRITE ^a(1), ^a(2), ^a(3)
^
^a(1)— Nem, nem maradt meg.
Próbáljuk ki a visszavonó parancsot:
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)Szintén semmi nem maradt meg.
2. Konzisztencia
Mivel a globális adatbázisokban a kulcsok szintén globális szinten készülnek (emlékezzünk, hogy a globális egy alacsonyabb szintű adatstruktúra a relációs táblákhoz képest), a konzisztencia követelményének teljesítése érdekében a kulcs módosítását ugyanabba a tranzakcióba kell foglalni, mint a globális módosítását.
Például van egy globális ^person, ahol a személyi adatokat tároljuk, és a kulcsunk a TAJ szám.
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...Ahhoz, hogy gyorsan kereshessünk vezetéknév és keresztnév alapján, létrehoztunk egy ^index kulcsot.
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1Ahhoz, hogy az adatbázis konzisztens legyen, a személyi adatokat így kell hozzáadnunk:
TSTART
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
TCOMMITEnnek megfelelően törléskor is tranzakciót kell használnunk:
TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMITMás szóval a konzisztencia követelményének teljesítése teljes mértékben a programozó vállán nyugszik. De amikor globálisokról van szó – ez normális, alacsony szintű természetük miatt.
3. Izoláltság
Itt kezdődnek a bonyodalmak. Sok felhasználó dolgozik egyszerre ugyanazon az adatbázison, módosítva ugyanazokat az adatokat.
A helyzet összehasonlítható azzal, amikor sok felhasználó egyszerre dolgozik ugyanazon kódrepositoryn, és próbálnak egyszerre sok fájlban változtatásokat elkövetni.
Az adatbázisnak mindezt valós időben kell kezelnie. Figyelembe véve, hogy a komolyabb cégeknél van egy speciális ember, aki a verziókezelésért felel (ágak egyesítése, konfliktusok megoldása stb.), és az adatbázisnak mindezt valós időben kell tennie, nyilvánvalóvá válik a feladat bonyolultsága és az adatbázis helyes tervezésének szükségessége, valamint a hozzá kapcsolódó kódnak.
Az adatbázis nem képes megérteni a felhasználók által végrehajtott műveletek jelentését a konfliktusok elkerülése érdekében, ha azok ugyanazon adatokon dolgoznak. Csak egy tranzakciót tud visszavonni, amely ellentmond egy másiknak, vagy ezeket sorban végrehajtani.
Egy másik probléma, hogy a tranzakció végrehajtása során (a commit előtt) az adatbázis állapota inkonzisztens lehet, ezért ajánlott, hogy más tranzakciók ne férhessenek hozzá az inkonzisztens adatbázis állapothoz, ami relációs adatbázisokban sokféle módon valósítható meg: snapshotok létrehozása, több verziós sorok stb.
Párhuzamos tranzakciók végrehajtása során fontos, hogy ne zavarják egymást. Ez az izolációs tulajdonság.
Az SQL négy izolációs szintet határoz meg:
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
Tekintsük meg mindegyik szintet külön-külön. A megvalósítás költségei majdnem exponenciálisan növekednek.
READ UNCOMMITTED — ez a legalacsonyabb izolációs szint, de egyben a leggyorsabb is. A tranzakciók olvashatják egymás által végrehajtott módosításokat.
READ COMMITTED — ez a következő izolációs szint, amely kompromisszumnak számít. A tranzakciók nem olvashatják egymás által végrehajtott módosításokat a commit előtt, de olvashatják azokat a módosításokat, amelyeket a commit után hajtottak végre.
Ha van egy hosszú T1 tranzakciónk, amely alatt commitok történnek a T2, T3 … Tn tranzakciókban, amelyek ugyanazokkal az adatokkal dolgoznak, mint a T1, akkor az T1 adatlekérése során mindig eltérő eredményt kapunk. Ezt a jelenséget nem ismételhető olvasásnak hívják.
REPEATABLE READ — ebben az izolációs szintben nincs nem ismételhető olvasás jelensége, mivel minden adatlekérési kéréshez adatsnapshotot készítenek, és a tranzakció során a snapshotból származó adatokat használják. Azonban ebben az izolációs szintben lehetséges a fantom adatok olvasása. Ez alatt a párhuzamosan befejezett tranzakciók által hozzáadott új sorok olvasását értjük.
SERIALIZABLE — a legmagasabb izolációs szint. Az jellemzi, hogy a tranzakcióban bármilyen módon használt (olvasott vagy módosított) adatok más tranzakciók számára csak az első tranzakció befejezése után válnak elérhetővé.
Először nézzük meg, van-e műveletek izolálása a tranzakción belül a fő szálból. Nyissunk meg 2 terminálablakot.
Kill ^t
Write ^t(1)
2
TSTART
Set ^t(1)=2Nincs izoláció. Az egyik szál látja, mit csinál a másik, aki megnyitotta a tranzakciót.
Nézzük meg, hogy a különböző szálak tranzakciói látják-e, mi történik bennük.
Nyissunk meg 2 terminálablakot, és párhuzamosan nyissunk meg 2 tranzakciót.
kill ^t
TSTART
Write ^t(1)
3
TSTART
Set ^t(1)=3
A párhuzamos tranzakciók látják egymás adatait. Így a leg egyszerűbb, de leggyorsabb izolációs szintet, a READ UNCOMMITTED-et kaptuk.
Ezt alapvetően várhattuk a globális adatoknál, ahol a sebesség mindig kiemelt szerepet játszott.
Mi a teendő, ha magasabb izolációs szintre van szükségünk a globális műveletekben?
Itt érdemes elgondolkodni, miért is szükségesek az izolációs szintek és hogyan működnek.
A legmagasabb izolációs szint, a SERIALIZE azt jelenti, hogy a párhuzamosan végrehajtott tranzakciók eredménye egyenértékű a sorozatos végrehajtásukkal, garantálva a ütközések hiányát.
Ezt úgy érhetjük el, hogy tudatosan kezeljük a zárolásokat az ObjectScript-ben, amelyeknek számos, különböző alkalmazási módja létezik: lehetőségünk van normál, inkrementális, vagy többszörös zárolás végrehajtására a .
Alacsonyabb izolációs szintek – kompromisszumok, amelyek célja a számítógép sebességének növelése.
Nézzük meg, hogyan érhetjük el a különböző izolációs szinteket zárolások segítségével.
Ez a parancs nemcsak az adatok módosításához szükséges exkluzív zárolásokat enged, hanem úgynevezett megosztott zárolásokat is, amelyek egyidejűleg több szál által is megszerezhetők, amikor az adatok olvasásához van szükség, amelyeket más folyamatok nem módosíthatnak az olvasás során.
További információ a képház módszerről oroszul és angolul:
→
→
A probléma az, hogy a tranzakció során az adatbázis állapota következetlen lehet, azonban ezek az inkonzisztens adatok más folyamatok számára láthatók. Hogyan lehet ezt elkerülni?
Olyan láthatósági ablakokat hozunk létre zárolásokkal, amelyekben az adatbázis állapota következetes lesz. Minden ilyen következetes állapothoz való hozzáférést zárolások irányítanak.
A megosztott zárolások ugyanazokhoz az adatokhoz többszörösen is rendelhetők - több folyamat is felveheti azokat. Ezek a zárolások megakadályozzák más folyamatok számára az adatok módosítását, vagyis azokat a DB konzisztens állapotának kialakítására használják.
Az exkluzív zárolásokat adatok módosítására használják - ilyen zárolást csak egyetlen folyamat vehet fel. Exkluzív zárolást vehet fel:
- Bármely folyamat, ha az adatok szabadok.
- Csak az a folyamat, amelynek van shared zárolása ezekhez az adatokhoz, és elsőként kérte az exkluzív zárolást.

Minél szűkebb a láthatósági ablak, annál hosszabb ideig kell várniuk más folyamatoknak, de annál inkább konzisztens lehet a DB állapota benne.
READ_COMMITED — ezen szint lényege, hogy csak a más folyamatokból lezártak adatait látjuk. Ha egy másik tranzakcióban az adatok még nincsenek lezárva, akkor annak régi verzióját látjuk.
Ez lehetővé teszi számunkra a párhuzamos munkát a zárolások feloldásának várakozása helyett.
Speciális trükkök nélkül nem láthatjuk az adatok régi verzióját az IRIS-ben, ezért zárolásokra lesz szükségünk.
Ennek megfelelően shared zárolások segítségével csak a konzisztenciák pillanataiban engedélyezhetjük az adatok olvasását.
Tegyük fel, hogy van egy felhasználói adatbázisunk ^person, akik pénzt utalnak egymásnak.
A pénz átutalásának pillanata a 123. és a 242. személy között:
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)A 123. személy pénzösszegének lekérdezése a levonás előtt exkluzív zárolást kell, hogy kísérjen (alapértelmezetten):
LOCK +^person(123)
Write ^person(123)Ha azonban a számlakivonat megjelenítése a felhasználói fiókban szükséges, akkor használhatunk shared zárolást, vagy akár el is hagyhatjuk azt:
LOCK +^person(123)#”S”
Write ^person(123)Azonban, ha feltételezzük, hogy az adatbázissal végzett műveletek majdnem azonnal végrehajtódnak (emlékeztetem, hogy a globálok egy sokkal alacsonyabb szintű struktúra, mint a relációs táblázat), akkor ezen szint szükségessége csökken.
REPEATABLE READ — ezen a szintű izolációnál engedélyezett, hogy több adatolvasás is történjen, amelyeket párhuzamos tranzakciók módosíthatnak.
Ennek megfelelően shared zárolást kell alkalmaznunk az adatok olvasásához, amelyeket módosítunk, és exkluzív zárolásokat az adatokra, amelyeket megváltoztatunk.
A LOCK operator allows you to detail all necessary locks within a single operator, which can be numerous.
LOCK +^person(123, amount)#”S”
reading ^person(123, amount)other operations (at this time, parallel streams attempt to modify ^person(123, amount), but cannot)
LOCK +^person(123, amount)
modifying ^person(123, amount)
LOCK -^person(123, amount)
reading ^person(123, amount)
LOCK -^person(123, amount)#”S”When listing locks with commas, they are taken sequentially, but if done like this:
LOCK +(^person(123),^person(242))they are taken atomically all at once.
SERIALIZE — we will have to set the locks in such a way that ultimately all transactions with shared data are executed sequentially. For this approach, most locks should be exclusive and taken on the smallest areas of the global for performance.
If we talk about withdrawals in the global ^person, only the SERIALIZE isolation level is acceptable, as money must be spent strictly sequentially; otherwise, it is possible to spend the same amount multiple times.
4. Longevity
I conducted tests with a hard shutdown of the container by
docker kill my-irisThe database handled it well. No issues were identified.
Zárás
For globals in InterSystems IRIS, there is support for transactions. They are truly atomic and reliable. However, to ensure database consistency in globals, programmer effort and the use of transactions are necessary, as it lacks complex built-in constructs like foreign keys.
The isolation level for globals without using locks is READ UNCOMMITTED, but with locks, it can be secured up to the SERIALIZE level.
The correctness and speed of transactions in globals greatly depend on the programmer's skill: the more broadly shared locks are used when reading, the higher the isolation level; the more narrowly exclusive locks are taken, the greater the performance.
Forrás: habr.com
