La base de données InterSystems IRIS prend en charge des structures de données intéressantes : les globals. Il s'agit en réalité de clés multi-niveaux avec diverses fonctionnalités supplémentaires telles que des transactions, des fonctions rapides pour naviguer dans les arbres de données, des verrous et son propre langage, l'ObjectScript.
Pour en savoir plus sur les globals, consultez la série d'articles « Globals - des épées-léviers pour le stockage des données » :
Je me suis intĂ©ressĂ© Ă la maniĂšre dont les transactions sont mises en Ćuvre dans les globals et aux spĂ©cificitĂ©s associĂ©es. En effet, il s'agit d'une structure de stockage de donnĂ©es complĂštement diffĂ©rente des tables habituelles. Beaucoup plus bas niveau.
Comme il est connu dans la thĂ©orie des bases de donnĂ©es relationnelles, une bonne mise en Ćuvre des transactions doit satisfaire aux exigences suivantes : :
A â Atomique. Tous les changements effectuĂ©s dans la transaction sont enregistrĂ©s ou aucun.
C â CohĂ©rence. AprĂšs la fin de la transaction, l'Ă©tat logique de la base de donnĂ©es doit ĂȘtre intrinsĂšquement cohĂ©rent. Cette exigence concerne en grande partie le programmeur, mais dans le cas des bases de donnĂ©es SQL, elle concerne Ă©galement les clĂ©s Ă©trangĂšres.
I â Isolation. Les transactions exĂ©cutĂ©es simultanĂ©ment ne doivent pas s'influencer mutuellement.
D â DurabilitĂ©. AprĂšs une transaction rĂ©ussie, des problĂšmes au niveau infĂ©rieur (comme une panne de courant, par exemple) ne doivent pas avoir d'effet sur les donnĂ©es modifiĂ©es par la transaction.
Les globals sont des structures de donnĂ©es non relationnelles. Elles ont Ă©tĂ© conçues pour un fonctionnement ultra-rapide sur du matĂ©riel trĂšs limitĂ©. Analysons la mise en Ćuvre des transactions dans les globals Ă l'aide de .
Pour prendre en charge les transactions dans IRIS, on utilise les commandes : , , .
1. Atomicité
Il est le plus facile de vérifier l'atomicité. Vérifions depuis la console de la base de données.
Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMITEnsuite, faisons une sortie :
Write ^a(1), " ", ^a(2), " ", ^a(3)Nous obtiendrons :
1 2 3Tout est en ordre. L'atomicité est respectée : tous les changements ont été enregistrés.
Compliquons un peu la tùche, introduisons une erreur et voyons comment la transaction est conservée, partiellement ou pas du tout.
Vérifions à nouveau l'atomicité :
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3Ensuite, nous forcerons l'arrĂȘt du conteneur, le relancerons et regarderons.
docker kill my-irisCette commande est pratiquement Ă©quivalente Ă une coupure de courant brutale, car elle envoie un signal d'arrĂȘt immĂ©diat du processus SIGKILL.
Une transaction a-t-elle pu ĂȘtre enregistrĂ©e partiellement ?
WRITE ^a(1), ^a(2), ^a(3)
^
^a(1)â Non, cela ne s'est pas enregistrĂ©.
Testons la commande 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)
^
^a(1)Rien non plus ne s'est enregistré.
2. Cohérence
Ătant donnĂ© que dans les bases de donnĂ©es, les clĂ©s sont Ă©galement créées sur des globaux (rappelons que le global est une structure de stockage de donnĂ©es de niveau infĂ©rieur par rapport Ă une table relationnelle), pour satisfaire Ă l'exigence de cohĂ©rence, il faut inclure la modification de la clĂ© dans la mĂȘme transaction que la modification du global.
Par exemple, nous avons un global ^person, oĂč nous stockons des personnalitĂ©s et utilisons comme clĂ© le numĂ©ro d'identification fiscale.
^person(1234567, 'firstname') = 'Sergey'
^person(1234567, 'lastname') = 'Kamenev'
^person(1234567, 'phone') = '+74995555555
...Pour avoir une recherche rapide par nom et prénom, nous avons créé la clé ^index.
^index('Kamenev', 'Sergey', 1234567) = 1Pour que la base de données soit cohérente, nous devons ajouter la personnalité comme suit :
TSTART
^person(1234567, 'firstname') = 'Sergey'
^person(1234567, 'lastname') = 'Kamenev'
^person(1234567, 'phone') = '+74995555555
^index('Kamenev', 'Sergey', 1234567) = 1
TCOMMITPar conséquent, lors de la suppression, nous devons également utiliser une transaction :
TSTART
Kill ^person(1234567)
ZKill ^index('Kamenev', 'Sergey', 1234567)
TCOMMITEn d'autres termes, le respect des exigences de cohérence repose entiÚrement sur les épaules du programmeur. Mais quand il s'agit de globaux, c'est normal en raison de leur nature de bas niveau.
3. Isolation
Ici, les choses se compliquent. Plusieurs utilisateurs travaillent simultanĂ©ment sur la mĂȘme base, modifiant les mĂȘmes donnĂ©es.
La situation est comparable Ă celle oĂč de nombreux utilisateurs travaillent simultanĂ©ment sur le mĂȘme dĂ©pĂŽt de code et essaient de soumettre des modifications dans plusieurs fichiers en mĂȘme temps.
La base de donnĂ©es doit gĂ©rer tout cela en temps rĂ©el. Ătant donnĂ© que dans des entreprises sĂ©rieuses, il existe mĂȘme une personne spĂ©ciale responsable du contrĂŽle des versions (pour le fusionnement des branches, la rĂ©solution des conflits, etc.), tandis que la base de donnĂ©es doit faire tout cela en temps rĂ©el, il devient Ă©vident que la tĂąche est complexe et que la conception de la base de donnĂ©es et du code qui la maintient est cruciale.
La base de donnĂ©es ne peut pas comprendre le sens des actions effectuĂ©es par les utilisateurs afin d'Ă©viter des conflits lorsqu'ils travaillent sur les mĂȘmes donnĂ©es. Elle ne peut que valider une transaction qui contredit une autre ou les exĂ©cuter successivement.
Un autre problĂšme est qu'au cours de l'exĂ©cution d'une transaction (avant le commit), l'Ă©tat de la base peut ĂȘtre incohĂ©rent. Il est donc souhaitable que d'autres transactions n'aient pas accĂšs Ă cet Ă©tat incohĂ©rent, ce qui est rĂ©alisĂ© dans les bases de donnĂ©es relationnelles de plusieurs maniĂšres : crĂ©ation de snapshots, multiversioning des lignes, etc.
Lors de l'exécution parallÚle des transactions, il est important qu'elles ne s'interfÚrent pas mutuellement. C'est ce qu'on appelle la propriété d'isolation.
SQL définit 4 niveaux d'isolation :
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
Examinons chaque niveau sĂ©parĂ©ment. Les coĂ»ts de mise en Ćuvre de chaque niveau augmentent presque de maniĂšre exponentielle.
READ UNCOMMITTED â c'est le niveau d'isolation le plus bas, mais aussi le plus rapide. Les transactions peuvent lire les modifications apportĂ©es par d'autres.
READ COMMITTED â c'est le niveau d'isolation suivant, qui constitue un compromis. Les transactions ne peuvent pas lire les modifications effectuĂ©es par d'autres avant le commit, mais peuvent lire toutes les modifications apportĂ©es aprĂšs le commit.
Si nous avons une longue transaction T1, pendant laquelle des commits ont eu lieu dans les transactions T2, T3,⊠Tn, qui ont travaillĂ© avec les mĂȘmes donnĂ©es que T1, alors lors de la requĂȘte dans T1, nous obtiendrons Ă chaque fois un rĂ©sultat diffĂ©rent. Ce phĂ©nomĂšne est appelĂ© lecture non rĂ©pĂ©table.
REPEATABLE READ â Ă ce niveau d'isolation, il n'y a pas de phĂ©nomĂšne de lecture non rĂ©pĂ©table, car pour chaque requĂȘte de lecture de donnĂ©es, un snapshot des donnĂ©es rĂ©sultantes est créé et, lors d'une rĂ©utilisation dans cette mĂȘme transaction, les donnĂ©es du snapshot sont utilisĂ©es. Cependant, Ă ce niveau d'isolation, il est possible de lire des donnĂ©es fantĂŽmes. Cela signifie lire de nouvelles lignes qui ont Ă©tĂ© ajoutĂ©es par des transactions parallĂšles validĂ©es.
SERIALIZABLE â le niveau d'isolation le plus Ă©levĂ©. Il se caractĂ©rise par le fait que les donnĂ©es utilisĂ©es d'une maniĂšre ou d'une autre dans la transaction (lecture ou modification) ne deviennent accessibles Ă d'autres transactions qu'aprĂšs la fin de la premiĂšre transaction.
Commençons par dĂ©terminer s'il existe une isolation des opĂ©rations dans la transaction par rapport au flux principal. Ouvrons 2 fenĂȘtres de terminal.
Kill ^t
Write ^t(1)
2
TSTART
Set ^t(1)=2Il n'y a pas d'isolation. Un flux voit ce que fait l'autre qui a ouvert la transaction.
Voyons si les transactions de différents flux voient ce qui se passe à l'intérieur d'elles.
Ouvrons 2 fenĂȘtres de terminal et ouvrons 2 transactions en parallĂšle.
kill ^t
TSTART
Write ^t(1)
3
TSTART
Set ^t(1)=3
Les transactions parallÚles se voient mutuellement leurs données. Ainsi, nous avons obtenu le niveau d'isolation le plus simple mais le plus rapide, READ UNCOMMITTED.
Cela pouvait ĂȘtre attendu pour les globaux, pour lesquels la performance a toujours Ă©tĂ© prioritaire.
Que faire si nous avons besoin d'un niveau d'isolation plus élevé pour les opérations sur les globaux ?
Il faut réfléchir à pourquoi il est nécessaire d'avoir des niveaux d'isolation et comment ils fonctionnent.
Le niveau d'isolation le plus élevé, SERIALIZE, signifie que le résultat des transactions exécutées en parallÚle est équivalent à leur exécution séquentielle, ce qui garantit l'absence de collisions.
Nous pouvons atteindre cela grĂące Ă des verrouillages adĂ©quats dans ObjectScript, qui ont de nombreuses façons d'ĂȘtre appliquĂ©s : on peut faire une verrouillage ordinaire, incrĂ©mentale ou multiple avec la commande .
Des niveaux d'isolation plus bas représentent des compromis visant à augmenter la vitesse de fonctionnement de la base de données.
Voyons comment nous pouvons atteindre différents niveaux d'isolation grùce aux verrouillages.
Cet opĂ©rateur permet de prendre non seulement des verrouillages exclusifs, nĂ©cessaires pour modifier des donnĂ©es, mais aussi des verrouillages partagĂ©s, qui peuvent ĂȘtre pris simultanĂ©ment par plusieurs flux lorsqu'ils ont besoin de lire des donnĂ©es qui ne doivent pas ĂȘtre modifiĂ©es par d'autres processus pendant la lecture.
Plus d'informations sur la méthode des verrouillages en deux phases en russe et en anglais :
â
â
La difficultĂ© rĂ©side dans le fait qu'au cours de la transaction, l'Ă©tat de la base de donnĂ©es peut ĂȘtre incohĂ©rent, cependant, ces donnĂ©es incohĂ©rentes sont visibles par d'autres processus. Comment Ă©viter cela ?
Nous crĂ©erons Ă l'aide de verrouillages des fenĂȘtres de visibilitĂ© dans lesquelles l'Ă©tat de la base sera cohĂ©rent. Et toutes les demandes Ă de telles fenĂȘtres de visibilitĂ© d'Ă©tat cohĂ©rent seront contrĂŽlĂ©es par des verrouillages.
Les verrous partagĂ©s sur les mĂȘmes donnĂ©es sont rĂ©utilisables : plusieurs processus peuvent les acquĂ©rir. Ces verrouillages interdisent Ă d'autres processus de modifier les donnĂ©es, c'est-Ă -dire qu'ils sont utilisĂ©s pour former des fenĂȘtres d'Ă©tat cohĂ©rent de la base de donnĂ©es.
Les verrous exclusifs sont utilisĂ©s pour modifier les donnĂ©es : un tel verrou peut ĂȘtre acquis uniquement par un seul processus. Un verrou exclusif peut ĂȘtre acquis par :
- Tout processus, si les données sont libres
- Uniquement le processus qui a un verrou partagé sur ces données et qui a demandé en premier le verrou exclusif.

Plus la fenĂȘtre de visibilitĂ© est Ă©troite, plus les autres processus doivent attendre longtemps, mais plus l'Ă©tat de la base de donnĂ©es peut ĂȘtre cohĂ©rent dans celle-ci.
READ_COMMITED â l'essence de ce niveau est que nous voyons uniquement les donnĂ©es validĂ©es d'autres flux. Si les donnĂ©es dans une autre transaction ne sont pas encore validĂ©es, nous voyons leur ancienne version.
Cela nous permet de paralléliser le travail au lieu d'attendre que le verrou soit libéré.
Sans astuces spéciales, nous ne pourrons pas voir l'ancienne version des données dans IRIS, il nous faudra donc nous contenter de verrous.
Par conséquent, nous devrons permettre la lecture des données uniquement lors de moments de cohérence à l'aide de verrous partagés.
Supposons que nous avons une base d'utilisateurs ^person qui se transfĂšrent de l'argent.
Le moment du transfert de la personne 123 Ă la personne 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)Le moment de la requĂȘte du montant d'argent chez la personne 123 avant le dĂ©bit doit ĂȘtre accompagnĂ© d'un verrou exclusif (par dĂ©faut) :
LOCK +^person(123)
Write ^person(123)Et si nous devons afficher l'état du compte dans l'espace personnel, nous pouvons utiliser un verrou partagé ou ne pas en utiliser du tout :
LOCK +^person(123)#âSâ
Write ^person(123)Cependant, supposons que les opérations de gestion de la base de données se déroulent presque instantanément (je rappelle que les globales sont une structure beaucoup plus basse niveau que la table relationnelle), alors la nécessité de ce niveau diminue.
REPEATABLE READ â Ă ce niveau d'isolation, il est permis qu'il y ait plusieurs lectures de donnĂ©es qui peuvent ĂȘtre modifiĂ©es par des transactions parallĂšles.
Par conséquent, nous devrons établir un verrou partagé sur les données que nous modifions et des verrous exclusifs sur les données que nous changeons.
L'opĂ©rateur LOCK permet de spĂ©cifier en dĂ©tail toutes les verrouillages nĂ©cessaires dans un seul opĂ©rateur, ce qui peut ĂȘtre trĂšs nombreux.
LOCK +^person(123, amount)#âSâ
lire ^person(123, amount)d'autres opérations (pendant ce temps, des flux parallÚles tentent de modifier ^person(123, amount), mais ne peuvent pas)
LOCK +^person(123, amount)
modifier ^person(123, amount)
LOCK -^person(123, amount)
lire ^person(123, amount)
LOCK -^person(123, amount)#âSâLors de l'Ă©numĂ©ration des verrouillages sĂ©parĂ©s par des virgules, ceux-ci sont pris sĂ©quentiellement, mais si l'on fait ainsi :
LOCK +(^person(123),^person(242))ils sont alors pris atomiquement tous en mĂȘme temps.
SĂRIALISER â nous devrons dĂ©finir des verrouillages de maniĂšre Ă ce que, finalement, toutes les transactions qui ont des donnĂ©es communes s'exĂ©cutent de maniĂšre sĂ©quentielle. Pour cette approche, la plupart des verrouillages doivent ĂȘtre exclusifs et pris aux plus petites rĂ©gions du global pour des performances optimales.
En ce qui concerne les dĂ©bits dans le global ^person, seul le niveau d'isolation SĂRIALISER est acceptable, car l'argent doit ĂȘtre dĂ©pensĂ© strictement de maniĂšre sĂ©quentielle, sinon il est possible de dĂ©penser la mĂȘme somme plusieurs fois.
4. Durabilité
J'ai effectué des tests avec une coupure brutale du conteneur par
docker kill my-irisLa base les a bien supportés. Aucun problÚme n'a été détecté.
Conclusion
Pour les globals dans InterSystems IRIS, le support des transactions est en place. Elles sont réellement atomiques et fiables. Cependant, pour garantir la cohérence de la base de données dans les globals, des efforts du programmeur et l'utilisation de transactions sont nécessaires, car il n'y a pas de constructions complexes intégrées comme les clés étrangÚres.
Le niveau d'isolation des globals sans utilisation de verrouillages est READ UNCOMMITTED, et avec l'utilisation de verrouillages, il peut atteindre jusqu'au niveau SĂRIALISER.
La justesse et la rapidité des transactions sur les globals dépendent fortement des compétences du programmeur : plus les verrouillages partagés sont largement utilisés lors de la lecture, plus le niveau d'isolation est élevé, et plus les verrouillages exclusifs sont étroitement pris, plus la rapidité est importante.
Source : habr.com
