BASES DE DONNÉES - Transactions - page 1/15 - Bertrand LIAUDET
Bases de données Transactions - ACID
Bertrand LIAUDET
LES TRANSACTIONS 2
Présentation des transactions 2
Présentation 2
Etat cohérent de la BD 2
Gestion des transactions par les SGBD 2
Propriétés ACID 3
Exemple 4
Atomicité : le problème des pannes 5
Principe : Start Transaction - Commit - Rollback 5
COMMIT implicite 5
Remarque : suspendre l’intégrité référentielle : attention : toujours dans une
transaction 5
Mode AUTOCOMMIT 6
Mode sans AUTOCOMMIT 6
Gestion de l’AUTOCOMMIT 6
Exemple - TP 7
Isolation : le problème de la concurrence 8
Présentation 8
Le problème 8
Exemple sans transaction : risque de double réservation 9 Exemple avec une transaction sans isolation (niveau 1 : read uncommited ) : risque
d’annulation complète 10
Les 4 niveaux d’isolation MySQL 11
Notion de verrou 12
Notion de deadlock 12
Conclusion 12
Consulter et modifier la valeur du niveau d’isolation : variable tx_isolation 13
Exemple – TP 14
Bibliographie 15
Edition : février 2020
BASES DE DONNÉES - Transactions - page 2/15 - Bertrand LIAUDET
LES TRANSACTIONS
Présentation des transactions Présentation
Une transaction est un ensemble de requêtes élémentaires du DML (insert, update, delete) qui doivent être exécutées ensemble pour maintenir la cohérence de la base.
L’objectif principal de la gestion des transactions est de maintenir la cohérence de la BD et de son usage.
Etat cohérent de la BD
Une BD est dans un état cohérent si les valeurs contenues dans la base vérifient toutes les contraintes d’intégrité définies sur la base.
Gestion des transactions par les SGBD
La gestion des transactions est un mécanisme standard SQL.
Attention, le moteur MyISAM ne gère pas les transactions.
Les moteurs InnoDB, Berkeley DB, NDB Cluster gèrent les transactions.
BASES DE DONNÉES - Transactions - page 3/15 - Bertrand LIAUDET
Propriétés ACID
Une transaction doit être ACID :
• Atomique : tout ou rien.
• Cohérente : maintenir la cohérence de la BD.
• Isolée : être indépendantes des autres transactions.
• Durable : ses effets doivent être persistants.
La durabilité est un principe de base des SGBD.
La cohérence est une conséquence des contraintes d’intégrité, de l’atomicité et de l’isolation.
On va donc surtout s’intéresser aux problèmes de l’atomicité et de l’isolation.
BASES DE DONNÉES - Transactions - page 4/15 - Bertrand LIAUDET
Exemple
Une transaction qui transfère 1000 Euros du compte A vers le compte B Cette transaction se déroule en deux étapes :
• Mettre à jour le compte A : ajouter une opération (date, montant, compte)
• Mettre à jour le compte B : ajouter une opération (date, montant, compte) La transaction
Mettre à jour le compte A 1. UPDATE comptes
SET balance = balance - 10000 WHERE no_compte = compteA;
// Mettre à jour le compte B 2. UPDATE comptes
SET balance = balance + 10000 WHERE no_compte = compteB;
Le problème
La base est cohérente si les deux instructions sont exécutées.
Il faut que les 2 instructions s’exécutent, ou rien.
Si la transaction "échoue" avant l’étape 2, alors la BD sera incohérente.
Echouer
Une transaction échoue si elle ne va pas jusqu’au bout de sa réalisation. Dans ce cas, on revient à l’état initial.
BASES DE DONNÉES - Transactions - page 5/15 - Bertrand LIAUDET
Atomicité : le problème des pannes
Principe : Start Transaction - Commit - Rollback
Pour être atomique, une transaction doit valider toutes ses requêtes élémentaires ou aucunes.
• START TRANSACTION : permet de marquer le début de la transaction.
• COMMIT : permet de valider la transaction : toutes les requêtes élémentaires sont désormais validées durablement.
• ROLLBACK : tant que le COMMIT n’est pas exécuté, un ROLLBACK permet de revenir en arrière. C’est ce que fera automatiquement le système en cas de panne au milieu de la transaction. Le programmeur peut aussi le faire s’il le souhaite en fonction de la situation dans le programme.
COMMIT implicite
De nombreuses commandes implique un COMMIT implicite :
• START TRANSACTION
• Le passage en mode AUTOCOMMIT
• Le DDL : CREATE, ALTER, DROP, RENAME, pour tout objet.
• TRUNCATE TABLE (DROP + CREATE) : pour vider une table complètement.
• LOCK et UNLOCK TABLE : pour vérouiller une table.
Remarque : suspendre l’intégrité référentielle : attention : toujours dans une transaction Set @@foreign_key_cheks = 0 ; // suspend l’intégrité référentielle.
En suspendant l’intégrité référentielle, on peut saisir les données dans n’importe quel ordre. C’est à manier avec prudence et toujours au sein d’une transaction pour éviter que l’effet soit visibles par les autres clients.
BASES DE DONNÉES - Transactions - page 6/15 - Bertrand LIAUDET
Mode AUTOCOMMIT
• En mode AUTOCOMMIT, chaque requête est validée individuellement implicitement (un COMMIT a lieu après chaque requête automatiquement).
• C’est le fonctionnement par défaut de MySQL.
• START TRANSACTION stoppe l’AUTOCOMMIT jusqu’au prochain COMMIT permettant ainsi la gestion d’une transaction.
Mode sans AUTOCOMMIT
• En sans mode AUTOCOMMIT, chaque requête de DML ouvre implicitement une transaction qui s’achèvera au prochain COMMIT.
• La requête suivante s’ouvrira alors de nouveau implicitement une transaction.
• En sans mode AUTOCOMMIT, un START TRANSACTION effectue un COMMIT de requêtes DML précédentes et commence une transaction.
• ATTENTION : en sans mode AUTOCOMMIT, les modifications effectuées sur la BD par un autre client ne sont pas visibles tant qu’on n’a pas fait un COMMIT ou l’équivalent !
• CONCLUSION : mieux vaut rester en mode AUTOCOMMIT !!!
Gestion de l’AUTOCOMMIT
Afficher la valeur de l’AUTOCOMMIT
C’est une variable de niveau Serveur : on y accède par @@
Select @@autocommit; // 1 par défaut
Ou
Show variables like ‘%autocommit%’; // ON par défaut
Modifier la valeur de l’AUTOCOMMIT
La modification ne vaut que pour le client qui l’effectue.
Set @@autocommit=0 ; // mode sans autocommit
BASES DE DONNÉES - Transactions - page 7/15 - Bertrand LIAUDET
Exemple - TP
1. Ouvrez 3 clients.
2. Affichez l’autocommit dans chaque client.
3. Mettez l’autocommit du client 3 à 0. Affichez l’autocommit dans chaque client.
On va maintenant regarder l’effet du sans autocommit dans le client 3.
4. Créez la BD commit dans le client 1. Créez la table test(i int) dans la BD commit. Ajoutez 1 tuple de valeur 1 dans la BD test.
5. Affichez les tuples de la BD dans les 3 clients. Que constatez-vous ?
Réponse : Vous devez voir le tuple créé dans les 3 clients. En effet, le use database à l’effet d’un Commit.
6. Ajoutez un tuple 2 dans le client 1. Affichez les tuples de la BD dans les 3 clients. Que constatez-vous ?
Réponse : On ne voit pas le tuple dans le client 3.
7. Faites un commit dans le client 3. Affichez les tuples de la BD dans le client 3. Que constatez- vous ?
Réponse : On voit les tuples qui on été créés. Le commit à permit l’accès aux modifications de la BD pour le client sans autocommit.
On va maintenant gérer une transaction à partir du client 1.
8. Dans le client 1, démarrez une transaction et ajoutez un tuple 10 dans la BD test sans faire de commit.
9. Affichez les tuples de la BD test dans les 3 clients. Que constatez-vous ?
Réponse : Vous devez voir le tuple créé dans le client 1 mais pas dans les autres. En effet, la transaction n’a pas été commité : la modification n’est visible que pour le client 1.
10. Faites un comit dans le client 1 et affichez les tuples de la BD test dans les 3 clients. Que constatez-vous ?
Réponse : Vous devez voir le tuple créé dans le client 1 et 2mais pas dans le 3. En effet, la transaction a été validée : les résultats sont visibles à condition de ne pas être sans autocommit.
11. Faites un commit dans le client 3. Affichez les tuples de la BD dans le client 3. Que constatez- vous ?
Réponse : On voit les tuples qui on été créés. Le commit à permit l’accès aux modifications de la BD pour le client sans autocommit.
BASES DE DONNÉES - Transactions - page 8/15 - Bertrand LIAUDET
Isolation : le problème de la concurrence Présentation
Pour éviter que deux utilisateurs ne modifient en même temps une donnée, les instructions du DML, insert, update et delete, verrouillent les lignes sur lesquelles elles travaillent.
En contexte transactionnelle, cette gestion se complique du fait que la transaction traite plusieurs requêtes élémentaires.
Le « niveau d’isolation » qui va définir la visibilité des modifications en cours dans une transaction.
Le problème
• Si une transaction n’est pas isolée, un autre utilisateur voit les états intermédiaires avant le commit.
• Si une transaction est isolée, un autre utilisateur ne voit pas les états intermédiaires mais ne voit que l’état qui précède la transaction et le résultat du commit de la transaction.
BASES DE DONNÉES - Transactions - page 9/15 - Bertrand LIAUDET
Exemple sans transaction : risque de double réservation On va poser le problème à partir d’un exemple.
Soit un processus de réservation : on vérifie si c’est disponible (select), puis on réserve (update ou insert).
Pas de transaction
SESSION 1 SESSION 2
Vérification : Select : ok
Vérification : Select : ok Réservation : Update ou insert : ok
Réservation : Update ou insert : ok => bug ! La vérification 2 est faite avant l’update 1. Du coup, l’update 2 passe. A la fin on a une double réservation ou une réservation (la 1) qui est écrasée par la 2.
BASES DE DONNÉES - Transactions - page 10/15 - Bertrand LIAUDET
Exemple avec une transaction sans isolation (niveau 1 : read uncommited ) : risque d’annulation complète
La transaction sans isolation est dite « read uncommited » : une modification non « commitée » est visible par tous les clients (par toutes les sessions). On peut lire les « uncommités ». Par contre on ne peut pas les modifier.
Cas où ça fonctionne :
Avec une transaction, sans isolation, on commence par réserver. Chacun peut faire sa réservation : ca génère une double réservation.
Ensuite on vérifie. Si la vérification échoue, il y a un ROLLBACK et une annulation de la première réservation : c’est bon.
Read Uncommitted : cas où ca passe
SESSION 1 SESSION 2
Réservation : Update ou insert : ok
Réservation : Update ou insert : ok Verification : Select : pas ok car on voit la
résa de droite ROLLBACK
Verification : Select : ok car il n’y a plus de résa à gauche.
COMMIT
Cas où ça ne fonctionne pas :
Si la SESSION 2 vérifie avant le ROLLBACK de la SESSION 1 : la vérification ne sera pas OK aussi dans la SESSION 2 et on aura 2 ROLLBACK et aucune réservation !
La lecture (vérification) dans la SESSION 2 est dite « lecture sale » (dirty read) : c’est une lecture sale car elle lit ce qui va être rollbacké.
Read Uncommitted : cas ou ça ne passe pas
SESSION 1 SESSION 2
Réservation : Update ou insert : ok
Réservation : Update ou insert : ok Vérification : Select : pas ok car on voit la
résa de droite
Vérification : Select : pas ok car on voit la résa de gauche
ROLLBACK
ROLLBACK
BASES DE DONNÉES - Transactions - page 11/15 - Bertrand LIAUDET
Les 4 niveaux d’isolation MySQL
N° Nom Description Erreur évitée + risque
1 Read Uncommited Lecture de données non validées
Pas d’isolation. Une modif non commitée est visible par tous les clients (par toutes les sessions). On peut lire les « uncommités ». Par contre on ne peut pas les modifier.
Risque : lecture sale
2 Read Committed Lecture de données validées
Une modif n’est visible que par le client qui l’a faite (que par sa session). On ne peut lire que les « commités ».
Lecture sale
Risque : lecture non répétable
3 Repeatable-Read Lecture répétée
Niveau par défaut: idem précédent mais avec une lecture cohérente : on ne lit pas les commités par d’autres au cours de la transaction.
C’est le niveau par défaut d’INNODB. Il utilise des « snapshot » qui sont des copies de données pour garder l’état initial.
Lecture non répétable Risque : lecture fantôme
4 Serializable Comme le précédent, avec émulation de
transactions les unes après les autres. Lecture fantôme Lecture sale : on lit quelque chose qui n’est pas encore validée et qui ne le sera pas.
Lecture non répétable : on lit quelque chose qui change en cours de transaction.
Lecture fantôme : on a lu quelque chose qui va changer ou disparaître en cours de transactions sans qu’on soit au courant.
https://docs.postgresql.fr/9.4/transaction-iso.html
BASES DE DONNÉES - Transactions - page 12/15 - Bertrand LIAUDET
Notion de verrou
Un verrou bloque l’accès en lecture ou en écriture à une ligne ou à une table.
Les verrous de table peuvent être gérés manuellement par les commandes LOCK TABLES et UNLOCK TABLES pour optimiser certains traitements.
Les verrous de ligne sont gérés automatiquement par les transactions : les transactions posent des verrous sur les lignes qu’elles utilisent.
Notion de deadlock
Les verrouillages peuvent conduire à un « dead lock » : un verrou mortel qui fait que le système est bloqué.
Un verrou mortel (impasse) est une situation dans laquelle différentes transactions ne peuvent pas être exécutées car chacune détient un verrou dont l'autre a besoin. Étant donné que deux transactions attendent qu'une ressource devienne disponible, on ne peut plus libérer les verrous qu'elles détiennent.
Toutefois, un blocage, même sans dead lock, est limité dans le temps : la variable
@@innodb_lock_wait_timeout fixe la limite (50 secondes par défaut).
Conclusion
Avec les verrous et les snapshot, l’isolation MySQL est correctement gérée. Dans le pire des cas, les deadlock sont arrêtés automatiquement.
BASES DE DONNÉES - Transactions - page 13/15 - Bertrand LIAUDET
Consulter et modifier la valeur du niveau d’isolation : variable tx_isolation
Consulter la variable niveau GLOBAL et celle niveau SESSION
SELECT @@GLOBAL.tx_isolation, @@SESSION.tx_isolation;
Rappel : une variable GLOBALE vaut pour tous les clients. Une variable LOCALE ne vaut que pour le client concerné.
Ou
Show variables like ‘%isolation%’; // REPEATABLE-READ par défaut Show global variables like ‘%isol%’; // REPEATABLE-READ par défaut
Modifier la variable niveau GLOBAL et celle niveau SESSION
SET GLOBAL tx_isolation='REPEATABLE-READ';
SET SESSION tx_isolation='SERIALIZABLE';
Ici, le serveur reste dans la configuration par défaut (REPEATABLE-READ). Par contre, au niveau client on passe en SERIALIZABLE. Ce qui veut dire qu’on interdit tout parallélisme et qu’on fait les transactions l’une après l’autre. Ca peut être mieux si il y a un risque que des lectures s’avèrent fausses et génèrent un échec de transaction. En forçant la transaction l’une après l’autre, on évite ça.
Ou encore :
SET LOCAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ; SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
Ici, le serveur et le client passe au plus bas niveau d’isolation.
A noter qu’un SET GLOBAL a un effet sur le serveur. Les futurs clients prendront en compte la nouvelle valeur du paramètre. Le SET LOCAL a un effet uniquement sur le client qui passe la commande.
BASES DE DONNÉES - Transactions - page 14/15 - Bertrand LIAUDET
Exemple – TP
1. Ouvrez 2 clients.
2. Affichez le niveau d’isolation local et global de chaque client.
3. Créez la BD isolation dans le client 1. Créez la table test(i int) dans la BD isolation. Démarrez une transaction. Créez 1 tuple de valeur 1 dans la BD test.
4. Affichez les tuples de la BD dans les 2 clients. Que constatez-vous ?
Réponse : Vous devez voir le tuple créé dans le client 1 et pas dans le client 2.
5. Commitez la transaction du client 1. Affichez les tuples de la BD dans les 2 clients. Que constatez-vous ?
Réponse : Vous devez voir le tuple créé dans les 2 clients.
6. Démarrez une transaction dans le client 1. Créez 1 tuple de valeur 2 dans la BD test. Updatez le tuple de valeur 1 de la table test dans le client 2. Que constatez-vous ? Attendez une minute.
Que constatez-vous ?
Réponse : L’update est bloqué dans le client 2. Au bout d’une minute, le blocage s’arrête sans fait l’update.
7. Affichez la valeur de la durée pour débloquer un verrou
Réponse : affichez @@innodb_lock_wait_timeout
8. Ajoutez un tuple de valeur 3 de la table test dans le client 2. Que constatez-vous ? Affichez les tuples de la table test dans le client 2.
Réponse : l’insert fonctionne. On peut afficher les résultats. On ne voit toujours pas le tuple 2.
9. Refaites un update des tuples en les passant tous à 1 dans le client 2. Tout de suite après, commitez le client 1. Affichez les tuples dans les 2 clients. Que constatez-vous ?
Réponse : l’update est bloqué dans le client 2. Quand on commite le client 1, ça débloque le client 2 : tous les tuples sont passés à 1.
BASES DE DONNÉES - Transactions - page 15/15 - Bertrand LIAUDET
Bibliographie MySQL :
http://bliaudet.free.fr/IMG/pdf/MySQL-refman-5.0-fr.pdf https://dev.mysql.com/doc/refman/5.5/en/
https://dev.mysql.com/doc/refman/5.6/en/
https://dev.mysql.com/doc/refman/5.7/en/
https://dev.mysql.com/doc/refman/8.0/en/
PostgreSQL
https://docs.postgresql.fr/9.4/tutorial-transactions.html https://docs.postgresql.fr/9.4/transaction-iso.html https://docs.postgresql.fr/9.4/mvcc.html
Autre cours :
https://makina-corpus.com/blog/metier/2015/bien-debuter-avec-les-transactions-sql