Chapitre 1317 minDébutant
Transactions SQL : COMMIT et ROLLBACK
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- regrouper des modifications avec
START TRANSACTION,COMMITetROLLBACK; - expliquer les propriétés ACID ;
- nommer les anomalies de concurrence et les niveaux d'isolation ;
- éviter un interblocage.
4 exercices corrigés2 schémas
Une transaction SQL regroupe des instructions que le SGBD valide toutes ensemble avec COMMIT, ou annule toutes avec ROLLBACK. Ce chapitre montre comment l'écrire sous MySQL, ce que garantissent les propriétés ACID, et comment les niveaux d'isolation et les verrous protègent les accès simultanés. Les exemples modifient la base d'exemple parc.
Pourquoi une transaction
L'exemple classique est le virement bancaire : débiter un compte, créditer l'autre. Si le serveur tombe entre les deux, l'argent a disparu. Les deux opérations doivent réussir ensemble ou échouer ensemble.
La base parc a le même besoin. Clôturer un ticket, c'est enregistrer la dernière intervention et passer le ticket au statut résolu. Une intervention enregistrée sur un ticket qui reste « en cours », ou un ticket clos sans trace de l'intervention, rend la base incohérente.
Une transaction est une suite d'instructions que le SGBD traite comme une seule : soit toutes ses modifications sont validées, soit aucune.
START TRANSACTION, COMMIT et ROLLBACK
START TRANSACTION;
INSERT INTO intervention (id_ticket, id_technicien, date_intervention, duree_min, compte_rendu)
VALUES (6, 12, '2026-10-28 09:00:00', 25, 'Ventilateur remplacé');
UPDATE ticket
SET statut = 'resolu', date_cloture = '2026-10-28 09:30:00'
WHERE id_ticket = 6;
COMMIT;
START TRANSACTION(ouBEGIN) ouvre la transaction ;COMMITvalide : les modifications deviennent définitives et visibles des autres sessions ;ROLLBACKannule tout ce qui a été fait depuisSTART TRANSACTION.
Si une instruction échoue, MySQL annule cette instruction, mais la transaction reste ouverte avec les instructions précédentes en attente. C'est à toi, ou à ton programme, de décider : corriger et continuer, ou annuler.
START TRANSACTION;
UPDATE ticket
SET statut = 'resolu', date_cloture = '2026-10-28 09:30:00'
WHERE id_ticket = 6;
INSERT INTO intervention (id_ticket, id_technicien, date_intervention, duree_min, compte_rendu)
VALUES (6, 12, '2026-10-28 09:00:00', 0, 'Ventilateur remplacé');
-- ERROR 3819 (HY000): Check constraint 'ck_intervention_duree' is violated.
ROLLBACK;
SELECT statut FROM ticket WHERE id_ticket = 6;
| statut |
|---|
| en_cours |
Le ROLLBACK a aussi annulé l'UPDATE qui avait réussi.
Par défaut, MySQL est en mode autocommit (SELECT @@autocommit; renvoie 1) : chaque instruction hors d'un START TRANSACTION est une transaction à elle seule, validée immédiatement. SET autocommit = 0; change ce comportement pour la session : chaque instruction ouvre alors une transaction qui attend un COMMIT explicite. Les clients graphiques ont souvent un bouton pour basculer entre les deux modes.
Autre différence avec PostgreSQL : une erreur à l'intérieur d'une transaction la rend inutilisable, et toutes les instructions suivantes sont refusées (current transaction is aborted) jusqu'au ROLLBACK. MySQL, lui, laisse la transaction continuer. Dans les deux cas, la bonne pratique est la même : sur une erreur, ton programme annule (erreurs et transactions côté code).
SAVEPOINT
Un point de sauvegarde permet d'annuler une partie de la transaction seulement :
START TRANSACTION;
UPDATE logiciel SET nb_licences = 10 WHERE id_logiciel = 5;
SAVEPOINT avant_installation;
INSERT INTO installation (id_poste, id_logiciel, date_installation)
VALUES (9, 5, '2026-10-28');
ROLLBACK TO SAVEPOINT avant_installation; -- annule l'INSERT, garde l'UPDATE
COMMIT;
Après le COMMIT, Odoo a 10 licences et le poste 9 n'a toujours pas Odoo. RELEASE SAVEPOINT nom supprime un point de sauvegarde sans rien annuler. Les points de sauvegarde servent surtout dans les procédures stockées, pour traiter une erreur sur une étape sans perdre les étapes précédentes.
Les propriétés ACID
Une transaction garantit quatre propriétés, résumées par l'acronyme ACID :
| Propriété | Garantie | Comment InnoDB la tient |
|---|---|---|
| Atomicité | Tout ou rien | Le journal d'annulation (undo log) permet de défaire les modifications |
| Cohérence | La base passe d'un état valide à un état valide | Les contraintes sont vérifiées à chaque instruction ; les règles métier, elles, dépendent de tes transactions |
| Isolation | Les transactions simultanées ne se perturbent pas, selon un niveau choisi | Versions multiples des lignes (MVCC) et verrous |
| Durabilité | Une transaction validée survit à une panne | Le journal de reprise (redo log) est écrit sur disque à chaque COMMIT |
Les anomalies de concurrence
Plusieurs utilisateurs travaillent en même temps sur la même base. Sans isolation, plusieurs anomalies peuvent survenir. Chaque tableau se lit de haut en bas, dans l'ordre chronologique.
Lecture sale (dirty read) : B lit une modification que A n'a pas validée, et que A annule ensuite.
| Session A | Session B |
|---|---|
START TRANSACTION; | |
UPDATE logiciel SET nb_licences = 20 WHERE id_logiciel = 5; | |
SELECT nb_licences FROM logiciel WHERE id_logiciel = 5; lit 20 | |
ROLLBACK; | B a utilisé une valeur qui n'a jamais existé |
Lecture non reproductible : B lit deux fois la même ligne dans sa transaction et obtient deux valeurs, parce que A l'a modifiée et validée entre les deux.
Lecture fantôme : B exécute deux fois la même requête et voit apparaître une ligne nouvelle (un « fantôme »), insérée et validée par A entre les deux. Par exemple, SELECT COUNT(*) FROM ticket WHERE statut = 'ouvert' donne 3, puis 4.
Mise à jour perdue (lost update) : deux sessions lisent la même valeur, calculent chacune une nouvelle valeur, et la seconde écriture écrase la première. Deux techniciens complètent la durée de l'intervention 9, enregistrée à 15 minutes :
| Session A | Session B |
|---|---|
lit duree_min : 15 | |
lit duree_min : 15 | |
écrit 15 + 20 = 35, puis COMMIT | |
écrit 15 + 10 = 25, puis COMMIT : les 20 minutes de A sont perdues |
Décision sur une lecture périmée (en anglais write skew, une anomalie de la même famille) : chaque session lit des données, décide d'après ce qu'elle a lu, puis écrit. Rien n'est écrasé, mais chacune a décidé sur une lecture que l'autre a rendue fausse entre-temps. C'est le scénario de la dernière licence Odoo :
Les niveaux d'isolation
La norme SQL définit quatre niveaux d'isolation. Plus le niveau est élevé, moins il y a d'anomalies possibles, et plus les transactions risquent de s'attendre.
| Niveau | Lecture sale | Lecture non reproductible | Lecture fantôme |
|---|---|---|---|
READ UNCOMMITTED | Possible | Possible | Possible |
READ COMMITTED | Impossible | Possible | Possible |
REPEATABLE READ | Impossible | Impossible | Possible selon la norme |
SERIALIZABLE | Impossible | Impossible | Impossible |
InnoDB utilise REPEATABLE READ par défaut, PostgreSQL et SQL Server READ COMMITTED. Dans InnoDB, en REPEATABLE READ, la première lecture ordinaire d'une transaction prend une photo cohérente de la base (un snapshot) : toutes les lectures ordinaires suivantes de la transaction voient cette photo, même si d'autres sessions valident des modifications entre-temps. Les lectures non reproductibles et les fantômes sont donc évités pour les SELECT ordinaires. En READ COMMITTED, chaque lecture prend une nouvelle photo.
SELECT @@transaction_isolation; -- REPEATABLE-READ
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- pour la session
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- pour la prochaine transaction seulement
Verrous, FOR UPDATE et interblocages
InnoDB pose des verrous sur les lignes qu'une transaction modifie. Une autre transaction qui veut modifier les mêmes lignes attend que la première se termine par COMMIT ou ROLLBACK. Les lectures ordinaires, elles, ne sont pas bloquées : elles lisent la photo cohérente.
Pour lire une ligne et empêcher les autres de la modifier avant toi, utilise une lecture verrouillante :
SELECT ... FOR UPDATEpose un verrou exclusif, comme le ferait unUPDATE: les autres transactions qui veulent modifier la ligne ou la lire avecFOR UPDATEattendent ;SELECT ... FOR SHAREpose un verrou partagé : les autres peuvent lire, pas modifier. MariaDB écritLOCK IN SHARE MODE, l'ancienne forme, que MySQL accepte encore.
Une lecture verrouillante lit toujours la dernière version validée, pas la photo. Elle n'a de sens que dans une transaction : en autocommit, le verrou serait libéré aussitôt. L'installation d'Odoo, sûre même si deux techniciens cliquent en même temps :
START TRANSACTION;
-- 1. Verrouiller la ligne du logiciel : un second technicien attendra ici
SELECT nb_licences FROM logiciel WHERE id_logiciel = 5 FOR UPDATE;
-- 2. Compter les installations, une fois le verrou obtenu
SELECT COUNT(*) FROM installation WHERE id_logiciel = 5;
-- 3. Si COUNT(*) < nb_licences : installer, sinon ROLLBACK
INSERT INTO installation (id_poste, id_logiciel, date_installation)
VALUES (9, 5, '2026-10-28');
COMMIT;
L'ordre compte. La photo d'une transaction REPEATABLE READ est prise à sa première lecture ordinaire : en verrouillant d'abord, tu garantis que le comptage a lieu après la validation de l'éventuel autre technicien. Le test « reste-t-il une licence ? » se fait dans le programme, entre l'étape 2 et l'étape 3 (exemple en PHP), ou dans une procédure stockée.
MySQL 8 ajoute deux options : FOR UPDATE NOWAIT échoue immédiatement si la ligne est verrouillée, et FOR UPDATE SKIP LOCKED ignore les lignes verrouillées, utile pour qu'un technicien prenne « le prochain ticket libre » dans une file sans attendre les autres.
Un interblocage (deadlock) se produit quand deux transactions s'attendent mutuellement :
| Session A | Session B |
|---|---|
START TRANSACTION; | START TRANSACTION; |
UPDATE ticket SET priorite = 'haute' WHERE id_ticket = 11; | |
UPDATE ticket SET priorite = 'haute' WHERE id_ticket = 13; | |
UPDATE ticket SET priorite = 'haute' WHERE id_ticket = 13; attend B | |
UPDATE ticket SET priorite = 'haute' WHERE id_ticket = 11; attend A |
InnoDB détecte le cycle immédiatement et annule l'une des deux transactions, en général celle qui a modifié le moins de lignes : elle reçoit l'erreur 1213, Deadlock found when trying to get lock; try restarting transaction. L'autre continue. Une attente sans interblocage, elle, s'arrête au bout de 50 secondes (variable innodb_lock_wait_timeout) avec l'erreur 1205, Lock wait timeout exceeded.
Exercices
Exercice 1 sur 4 Affecter un poste en une transaction Réussi À revoir
Paul Martin reçoit le poste PC-0014. En une transaction : affecte-lui le poste, enregistre une intervention de 40 minutes de Chloé Lambert (11) le 28 octobre 2026 à 14 h sur le ticket 7 (« Poste installé, Sage configuré »), installe Sage (4) sur le poste 14 et clôture le ticket 7 à 14 h 45.
Voir la réponseMasquer la réponse de l'exercice 1
START TRANSACTION;
UPDATE poste SET id_employe = 14 WHERE id_poste = 14;
INSERT INTO intervention (id_ticket, id_technicien, date_intervention, duree_min, compte_rendu)
VALUES (7, 11, '2026-10-28 14:00:00', 40, 'Poste installé, Sage configuré');
INSERT INTO installation (id_poste, id_logiciel, date_installation)
VALUES (14, 4, '2026-10-28');
UPDATE ticket
SET statut = 'resolu', date_cloture = '2026-10-28 14:45:00', id_poste = 14
WHERE id_ticket = 7;
COMMIT;
Si l'une des instructions échoue, on exécute ROLLBACK au lieu de COMMIT. Renseigner id_poste sur le ticket n'était pas demandé, mais rend l'historique plus exact.
Tu avais trouvé ?
Exercice 2 sur 4 Points de sauvegarde Réussi À revoir
Partant de la base initiale, quelles valeurs de priorite ont les tickets 6, 12 et 14 après cette suite d'instructions ?
START TRANSACTION;
UPDATE ticket SET priorite = 'haute' WHERE id_ticket = 6;
SAVEPOINT s1;
UPDATE ticket SET priorite = 'haute' WHERE id_ticket = 12;
SAVEPOINT s2;
UPDATE ticket SET priorite = 'haute' WHERE id_ticket = 14;
ROLLBACK TO SAVEPOINT s1;
UPDATE ticket SET priorite = 'critique' WHERE id_ticket = 14;
COMMIT;
SELECT id_ticket, priorite FROM ticket WHERE id_ticket IN (6, 12, 14) ORDER BY id_ticket;
Voir la réponseMasquer la réponse de l'exercice 2
| id_ticket | priorite |
|---|---|
| 6 | haute |
| 12 | basse |
| 14 | critique |
ROLLBACK TO SAVEPOINT s1 annule tout ce qui suit s1 : les modifications des tickets 12 et 14 (et le point s2 lui-même). La modification du ticket 6, antérieure à s1, est gardée. La transaction continue : la dernière mise à jour du ticket 14 est validée par le COMMIT.
Tu avais trouvé ?
Exercice 3 sur 4 Nommer l'anomalie Réussi À revoir
Pour chaque scénario, nomme l'anomalie et dis si InnoDB, au niveau par défaut, la laisse se produire.
- Le responsable lance un rapport dans une transaction. Il compte les tickets ouverts (3), puis les liste. Entre les deux, un employé ouvre un ticket et valide. La liste contient-elle 3 ou 4 tickets ?
- Un script lit
nb_licences(8), ajoute 2 dans le code PHP et écrit 10. Au même moment, un autre script lit 8, ajoute 5 et écrit 13. - Une transaction lit un ticket passé à « résolu » par un technicien qui annule ensuite sa modification.
Voir la réponseMasquer la réponse de l'exercice 3
- Lecture fantôme. En
REPEATABLE READ, InnoDB lit la même photo pour les deux requêtes : la liste contient 3 tickets, cohérente avec le comptage. EnREAD COMMITTED, elle en contiendrait 4. - Mise à jour perdue. Le résultat final est 10 ou 13, jamais 15. InnoDB la laisse se produire au niveau par défaut. La parade :
UPDATE logiciel SET nb_licences = nb_licences + 2, ou unSELECT ... FOR UPDATEavant le calcul. - Lecture sale. Impossible en
REPEATABLE READet enREAD COMMITTED: une transaction ne voit jamais des modifications non validées. SeulREAD UNCOMMITTEDle permet.
Tu avais trouvé ?
Exercice 4 sur 4 Ce qu'on ne peut pas annuler Réussi À revoir
Une stagiaire exécute ceci, puis s'inquiète. Qu'est-ce qui est annulé par le ROLLBACK, et qu'est-ce qui ne l'est pas ?
START TRANSACTION;
DELETE FROM installation WHERE id_poste = 13;
CREATE TABLE poste_archive LIKE poste;
DELETE FROM poste WHERE id_poste = 13;
ROLLBACK;
Voir la réponseMasquer la réponse de l'exercice 4
CREATE TABLE provoque un COMMIT implicite : le premier DELETE (les installations du poste 13) est validé à ce moment-là, et la table poste_archive est créée. Ce COMMIT a aussi terminé la transaction : la session est revenue en autocommit, et le DELETE FROM poste a été validé dès son exécution. Le ROLLBACK final n'a plus rien à annuler.
Résultat : les installations du poste 13 et le poste 13 lui-même sont supprimés, et poste_archive existe, vide. Garde le LDD hors des transactions, et rouvre une transaction après une instruction qui en a validé une implicitement.
Tu avais trouvé ?