326 lines
11 KiB
Markdown
326 lines
11 KiB
Markdown
---
|
|
title: "Base de données avancées : Mise en œuvre des transactions"
|
|
date: 2022-03-29
|
|
tags: ["transaction"]
|
|
categories: ["Base de données avancées", "Cours"]
|
|
mathjax: true
|
|
---
|
|
|
|
Une transaction est une unité d'un programme qui accède à des données d'un SGBDR
|
|
en lecture, écriture ou les deux. Une transaction accède à un **état cohérent**
|
|
de la base. Puis lors de son exécution l'état de la base **peut ne plus être
|
|
cohérent**. Lors de sa validation (on parle de *commit*), l'état de la base
|
|
**doit redevenir cohérent**.
|
|
|
|
Deux problèmes types peuvent se poser :
|
|
|
|
* problème systèmes: récupérabilité
|
|
* exécution concurente de plusieurs transactions: sériabilité.
|
|
|
|
Le système doit garantir les propriété ACID afin de preserver la cohérence des
|
|
données :
|
|
|
|
* Atomicité: soit **toutes** les opérations sont validées soit aucune.
|
|
* Cohérence: l'exécution d'une transaction jusqu'à sa validation doit laisser
|
|
la base dans un **état cohérent**.
|
|
* Isoliation: les transactions qui s'exécutent en concurence sont isolées les
|
|
unes des autres. Les modification effectuée par l'une ne doivent pas être
|
|
prise en comptes par une autre.
|
|
* Durabilité: si une transaction est validée, les modification qu'elle a
|
|
apportée à la base sont **persitantes**, même après le redémarrage du
|
|
service, une coupure de courant ou un crash
|
|
|
|
## L'exemple de la banque
|
|
|
|
Prenons un exemple de virement de 1000 euros d'un compte A vers un compte B:
|
|
|
|
1. Lire(A)
|
|
2. A = A - 1000
|
|
3. Ecrire(A)
|
|
4. Lire B
|
|
5. B - B + 1000
|
|
6. Ecrire(B)
|
|
|
|
Une fois la transaction terminée et validée, elle est réputée **durable**. Si
|
|
elle échoue à partir de l'etape 3, il faut s'assurer que les modifications ne
|
|
soient pas persistantes.
|
|
|
|
Les données sont alors **cohérente** : la somme de A est B est toujours la même.
|
|
|
|
Si une autre transaction accède à la base entre les étapes 3 et 6, elle trouvera
|
|
la base dans un état incohérent (A + B est inférieur et 1000 euros se
|
|
balladent...). **L'isolation** n'est pas assurée, la solution la plus simple
|
|
consisterai alors à **sérialiser** les transactions.
|
|
|
|
## État d'une transaction
|
|
|
|
Une transaction peut avoir 5 états:
|
|
|
|
* Active: c'est une transaction en cours d'exécution
|
|
* Partiellement validée: entre la dernière action et la validation
|
|
* Échec: lorque l'exécution normale de la transaction ne peut avoir lieu
|
|
* Avortée: après que toutes les modifications faite par la transaction soient
|
|
annulées (rollback). Il est alors possible de réexécuter la transaction ou de
|
|
la supprimer.
|
|
* Validée: après l'exécution réussie de la dernière opération.
|
|
|
|

|
|
|
|
## Implémentation de l'atomicité
|
|
|
|
### Approche naïve
|
|
|
|
Nous pouvons commencer par une approche naïve : **copie intégrale de la base
|
|
lors d'une opération** avec gestion d'un **pointeur** qui mème vers la dernière
|
|
version cohérente. Le pointeur est modifié seulement si la transaction réussie.
|
|
|
|
En plus de ne permettre l'exécution d'une seule transaction (pas de gestion de
|
|
l'exécution concurrente), cette approche est lourde et coûteuse surtout si la
|
|
base est volumineuse.
|
|
|
|
### L'exécution concurrente
|
|
|
|
Dans ce cas de figure, plusieurs transaction peuvent s'exécuter en même temps.
|
|
Cette approche apporte une **meilleure utilisation des ressources** ( voir les
|
|
[cours de système]({{< ref "/systemes_exploitation/1-introduction/index.md" >}}),
|
|
ainsi on réduit le temps de réponse: une longue transaction ne bloque plus les
|
|
autres.
|
|
|
|
**Le contrôle de la concurence** est le mécanisme permettant l'interaction entre
|
|
les transaction tout en garantissant l'intégrité de la base.
|
|
|
|
## Ordonnancement
|
|
|
|
Ici encore on peut se référer au cours de *système d'exploitation*.
|
|
L'ordonnancement défini la manière d'exécuter les transactions concurrentes.
|
|
|
|
### Ordonnancement en série
|
|
|
|
Soit \\(T_1\\) et \\(T_2\\) deux transactions ordonnancées par \\(O_1\\), en
|
|
série \\(T_2\\) sera exécutée lorsque \\(T_1\\) sera terminée.
|
|
|
|
### Ordonnancement entrelacé
|
|
|
|
Ici les actions réalisées par deux transaction s'exécutent de manières
|
|
entrelacées. Soit deux transactions \((T_2\)) et \((T_3\)), voici le plan
|
|
d'ordonnancement \\(O_2\\):
|
|
|
|
|
|
\\( \begin{array}{l|l}
|
|
T_1 & T_2 \\\
|
|
lire(A) & \\\
|
|
A := A + 1000 &\\\
|
|
ecrire(A) &\\\
|
|
& lire(A) \\\
|
|
& tmp := A * 0.2 \\\
|
|
& A := A - tmp \\\
|
|
& ecrire(A) \\\
|
|
lire(B) & \\\
|
|
B := B + 1000 & \\\
|
|
ecrire(B $ \\\
|
|
& lire(B) \\\
|
|
& B = B + tmp \\\
|
|
& ecrire(B) \\\
|
|
\end{array}
|
|
\\)
|
|
|
|
Dans l'exemple ci-dessus, l'entrelassement ne préserve pas les valeurs de A et
|
|
B. Les valeurs finales de notre version entrelacée ne correxpondent plus aux
|
|
valeurs de la version en série. **Nous sommes donc face à un problème**
|
|
|
|
### Notion de sériabilité
|
|
|
|
Dans le cas d'opération de lecture / écriture, nous partons du principe que
|
|
chaque transaction prise à part conserve la coherence de la base. Ainsi
|
|
l'ordonnancement en série préserve la cohérence.
|
|
|
|
Un ordonnancement entrelace est dit *sérialisable* si son résultat est
|
|
**exactement le même** que s'il est exécuté en série.
|
|
|
|
L'ordonnancement \\(O_f\\) composé de n transactions \\(T_1, T_2, ... T_n\\) est
|
|
sérialisable si toutes les permutations possibles ne changent pas le résultat.
|
|
Il faut donc tester \\(n!\\) possibilités de permutations.
|
|
|
|
Pour tester et trouver le bon ordonnancement, nous avons à notre disposition la
|
|
c-sériabilité.
|
|
|
|
### c-seriabilité
|
|
|
|
Les instructions notées \\(t_x\\) et \\(t_y\\) des transactions \\(T_x\\) et
|
|
\\(T_y\\) ordonnancées par \\(O_n\\) accèdent au même objet \\(Q\\):
|
|
|
|
* ne sont **pas en conflit** si elles accèdent toutes des deux à \\(Q\\) en
|
|
lecture
|
|
* sont **en conflit** si l'une des deux (ou les deux) accède à \\(Q\\)en
|
|
écriture
|
|
|
|
Si notre ordonnancement \\(O_n\\) peut-être transformé en \\(O_n^\text{'}\\) par
|
|
une série de remplacement d'instructions non conflictuelle alors \\(O_n\\) et
|
|
\\(O_n^\text{'}\\) sont dit **c-équivalent**. \\(O_n\\) est c-sérialisable s'il
|
|
est c-équivalent à un ordonnancement en série.
|
|
|
|
## Reprise sur panne, récupérabilité
|
|
|
|
un Ordonnancement \\(O\\) est récupérable si à la suite de l'annulation d'une de
|
|
ses transactions, nous pouvons revenir à un état cohérent sans pour autant
|
|
annuler les transaction validées.
|
|
|
|
Lors d'un **ordonnancement récupérable**, si \\(T_j\\) lit un objet précédement
|
|
écrit par \\(T_i\\), la validation de \\(T_i\\) a lieu avant celle de de
|
|
\\(T_j\\). Dans le cadre d'un *ordonnancement en série* le problème se se pose
|
|
pas, mais comme nous l'avons vu c'est une méthode inefficace, surtout sans le
|
|
cas d'achitecture multi-cœurs / multi-proceseurs.
|
|
|
|
C'est au SGBD de s'assurer de la récupérabilité des ordonnancements, mais cette
|
|
tâche parfois difficile, prenons l'exemple simple suivant:
|
|
|
|
|
|
\\( \begin{array}{l|l}
|
|
t_1 & t_2 \\\
|
|
lire(a) & \\\
|
|
a := a + 1000 &\\\
|
|
ecrire(a) &\\\
|
|
& lire(a) \\\
|
|
ecrire(b) & \\\
|
|
... & ... \\\\
|
|
\end{array}
|
|
\\)
|
|
|
|
Si \\(T_1\\) doit être annulée, alors la valeur de \\(A\\) pourrait-être dans un
|
|
état incohérent lorsque \\(T_1\\) y accède. Le SGBDR doit alors positionner le
|
|
curseur entre **ordonnancement** (réactivité, utilisation des ressources) et
|
|
**récupérabilité**.
|
|
|
|
Pour deux transactions \\(T_i\\), \\(T_j\\),si \\(T_j\\) accède une donnée
|
|
écrite précédement par \\(T_i\\) alors cette dernière doit être validée en
|
|
premier.
|
|
|
|
L'échec d'une transaction peut entrainer des annulations en cascade, dans
|
|
l'exemple suivant \\(T_2\\) et \\(T_3\\) doivent être annulé:
|
|
|
|
\\( \begin{array}{l|l}
|
|
t_1 & t_2 & t_3\\\
|
|
lire(a) & & \\\
|
|
a := a + 1000 & &\\\
|
|
ecrire(a) & &\\\
|
|
& lire(a) & \\\
|
|
& & lire(a) \\\
|
|
ecrire(b) & & \\\
|
|
... & ... & ... \\\
|
|
\end{array}
|
|
\\)
|
|
|
|
Un ordonnancement sans cascade est récupérable, il est donc préférable de
|
|
limiter les ordonnancement entrelacé à ceux sans cascade.
|
|
|
|
### tester la c-sériabilité
|
|
|
|
Considérons un ordonnancement \\(O\\) avec les transactions \\(T_1, T_2, ...
|
|
T_n\\) :
|
|
|
|
* \\(n\\) est l'ensemble des transactions
|
|
* il y a un arc \\((T_i, T_j)\\) s'il y a conflit entre ces deux transactions
|
|
sur un object \\(Q\\), \\(T_i\\) accède à \\(Q\\) avant \\(T_j\\).
|
|
|
|
Alors \\(O\\) est *c-serialisable* si son graphe de précédence est acyclique.
|
|
|
|
\\( \begin{array}{l|l|l|l|l}
|
|
t_1 & t_2 & t_3 & t_4 & t_5\\\
|
|
& lire(x) & & & \\\
|
|
lire(y) & & & & \\\
|
|
lire(z) & & & & \\\
|
|
& & & & lire(v) \\\
|
|
& & & & lire(w) \\\
|
|
& & & & ecrire(w) \\\
|
|
& lire(y) & & & \\\
|
|
& ecrire(y) & & & \\\
|
|
& & ecrire(z) & & \\\
|
|
lire(u) & & & & \\\
|
|
& & & lire(y) & \\\
|
|
& & & ecrire(y) & \\\
|
|
& & & lire(z) & \\\
|
|
& & & ecrire(z) & \\\
|
|
lire(u) & & & & \\\
|
|
ecrire(u) & & & \\\
|
|
\end{array}
|
|
\\)
|
|
|
|
|
|

|
|
|
|
Tester si l'ordonnancement est sérialisable après son exécution
|
|
ou sur son graphe de précédence est inefficace. Le but ici est donc de
|
|
développer des statégies de contrôle de la concurence qui puissent garantir la
|
|
c-sériabilité. Il ne sera pas question de faire des tests sur le grahe de
|
|
précédence mais plutôt les mécanisme de verrouillage que nous verrons un peu
|
|
plus bas.
|
|
|
|
## contrôle de la concurrence
|
|
|
|
### notion de verrouillage
|
|
|
|
Lorsque une transaction accède à une donnée, elle pose un verrou dessus, il
|
|
existe deux type de verrous:
|
|
|
|
* **exclusif**: la transaction qui pose se verrou peux *lire* et *écrire* la
|
|
donnée. Le verrou `X` est alors attribué à l'exécution via la commande
|
|
`Lock_X(data)`
|
|
* **partagé**: la transaction ne peut que *lire* la donnée. Le verrou `P` est
|
|
attribué via la commande `Lock_P(data)`
|
|
|
|
C'est le rôle du **gestionnaire de concurrence** de gérer l'attribution des
|
|
verrous à la demande des transaction. Il défini le protocole de verrouillage :
|
|
commend demander et libérer les verrous. Cette dernière ne peut avancer tant
|
|
que le verrou demandé ne lui a pas été attribué.
|
|
|
|
Une transaction ne peut poser un verrou sur une donnée qui si celui-ci est
|
|
compatible avec le verrou déjà en place (s'il y en a un bien entendu).
|
|
|
|
Une transaction peut libérer certain verrou avec la commande `Unlock()`.
|
|
|
|
### Gestion de l'interblocage (deadlock)
|
|
|
|
Comme nous l'avons vu pour les [Mecanisme de synchronisation]({{< ref
|
|
"/systemes_exploitation/3-synchronisation/index.md" >}}), dès qu'il y a verrou
|
|
il y a risque d'interblocage. Les transaction n'échappent pas à la règle.
|
|
|
|
### Protocole de verrou à deux phases
|
|
|
|
Ce protocole permet de garantir la *c-sériabilité*
|
|
|
|
* **phase 1**: pose de verrous, il est interdit d'en libérer.
|
|
* **phase 2**: libération des verrou, il est interdit d'en poser.
|
|
|
|
### protocole avec estampille
|
|
|
|
C'est un protocole avec la gestion d'une file d'attente en fonction de l'heure
|
|
d'arrivée: l'ordre chronologique des transactions.
|
|
|
|
### granularité des verrouillage
|
|
|
|
Le verroullage peut se faire à plusieurs niveau organisés hiérachiquement:
|
|
|
|
1. la base
|
|
2. une table
|
|
3. une page
|
|
4. un tuple
|
|
|
|
Une transaction verrouiller n'importe quel niveau en fonction de ses besoins.
|
|
|
|
### gestion des blocages
|
|
|
|
Le SGBDR peut intervenië sur deux niveaux : **prévenir** et *guérir*
|
|
|
|
#### prévenir
|
|
|
|
Il est question ici d'éviter les situations de blocage. L'estampille joue un
|
|
rôle crutial : la gestion des transaction se fait en fonction de son horodatage
|
|
(privilegier les plus anciennes)
|
|
|
|
#### guérir
|
|
|
|
Pour guérir mous pouvons
|
|
|
|
1. détecter les blocages à l'aide de graphe précédence
|
|
2. choisir d'ue transaction à annuler, en général celle la plus loin de son
|
|
état final.
|