Imagine que tu codes une appli pour une boutique en ligne, et qu'au moment du paiement d'une commande tu dois :
- Retenir l'argent sur la carte du client.
- Diminuer la quantité du produit en stock.
- Créer une entrée pour la transaction réussie.
Qu'est-ce qui se passe si, au milieu de ces actions, un truc foire ? Genre, le produit est en rupture de stock après que l'argent a été retenu, mais avant que l'entrée de commande soit créée ? C'est la cata : l'argent est "bloqué", la commande n'est pas terminée, et ton serveur reçoit des tonnes de mails furax (et peut-être même des procès).
Les transactions servent justement à éviter ce genre de galère. Elles permettent de regrouper plusieurs opérations en une seule unité "atomique" de boulot avec la base de données. C'est comme le bouton "Annuler" dans un éditeur de texte : si ça part en vrille, tu reviens juste au début.
Comment les transactions assurent l'intégrité des données ?
Les transactions sont basées sur le concept ACID :
- Atomicité (Atomicity) — Toutes les opérations dans la transaction sont faites à 100% ou rien du tout. "Tout ou rien".
- Cohérence (Consistency) — Les données restent cohérentes avant et après la transaction.
- Isolation (Isolation) — Une transaction ne gêne pas les autres.
- Durabilité (Durability) — Quand la transaction est finie, son résultat reste même si le système plante.
Pourquoi je répète ça encore ? Parce que c'est l'idéal que tout le monde vise. Et... qu'on atteint rarement. Quand on reviendra sur les transactions plus tard dans le cours, tu verras qu'il faudra parfois sacrifier certains principes ACID.
Alors profite du moment où les transactions sont simples et belles. Et passons aux exemples !
Exemple d'utilisation des transactions
Regardons un scénario où on ajoute un étudiant et on l'inscrit à un cours.
Imaginons qu'on bosse avec la base de données d'une université. On a des auditeurs libres pour nos cours. S'il reste de la place sur le cours, on inscrit cet auditeur comme étudiant (temporairement) et on l'ajoute au cours. Voilà comment ça se passe.
Quand on ajoute un nouvel étudiant dans la base et qu'on l'inscrit à un cours, il faut :
- Ajouter une entrée dans la table
students. - Créer une entrée dans la table
enrollmentsqui relie l'étudiant au cours.
Si un truc foire (genre, le cours est déjà plein), il faut annuler l'opération pour que les données restent synchro entre les tables. Voilà comment on fait :
-- Début de la transaction
BEGIN;
-- Étape 1 : On ajoute l'étudiant
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;
-- Disons qu'on récupère id = 10
-- Étape 2 : On l'inscrit au cours
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 5);
-- Tout s'est bien passé ? On valide les changements
COMMIT;
Que se passe-t-il si une erreur survient ?
Imaginons qu'il y ait une erreur à l'inscription au cours : genre, le cours n'existe pas. Si tu oublies la transaction, l'entrée de l'étudiant reste dans la table students, mais rien dans enrollments. Ça casse l'intégrité des données. Pour éviter ça, on peut utiliser la commande ROLLBACK.
-- Début de la transaction
BEGIN;
-- Étape 1 : On ajoute l'étudiant
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;
-- Étape 2 : On essaie de l'inscrire au cours
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 999); -- Erreur : pas de cours avec id = 999 !
-- On annule tous les changements
ROLLBACK;
Résultat : aucune des opérations n'est faite, et la base reste comme avant la transaction.
Utiliser SAVEPOINT pour le contrôle
Maintenant, imagine un scénario plus complexe. Tu veux faire plusieurs opérations, mais à un moment tu veux revenir juste à un point précis, pas tout annuler.
Faisons une inscription étape par étape d'un étudiant
-- Début de la transaction
BEGIN;
-- On ajoute l'étudiant
SAVEPOINT add_student; -- On crée un point de sauvegarde
INSERT INTO students (name, age, gender)
VALUES ('Anna Song', 22, 'Female');
-- On l'inscrit au premier cours
SAVEPOINT enroll_course_1; -- Encore un point de sauvegarde
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 5);
-- On l'inscrit au deuxième cours (erreur ici)
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 999); -- Erreur !
-- On revient juste au dernier point de sauvegarde
ROLLBACK TO enroll_course_1;
-- On continue le process
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 6);
-- On valide les changements
COMMIT;
Comme ça, une erreur dans une partie du process n'empêche pas de sauvegarder les autres données.
Vérifier s'il y a eu des changements
Si une requête SQL modifie quelque chose, tu peux checker s'il y a vraiment eu des changements ou pas.
Ça peut arriver qu'on fasse un DELETE, mais qu'aucune ligne ne corresponde au WHERE. Ou qu'on fasse un UPDATE, mais que les données soient déjà à jour, donc rien ne change vraiment.
Pour ça, il y a une variable système spéciale FOUND. Elle indique si des lignes ont été touchées dans la dernière requête SQL :
FOUND = TRUE— la requête a modifié/supprimé quelque chose ;FOUND = FALSE— rien n'a été supprimé ou modifié.
Avec un SELECT classique ça marche pas, c'est juste pour suivre les modifs.
Cas pratique : gestion des paiements
Les transactions sont super utiles dans les applis financières. Reprenons un système qui doit transférer de l'argent d'un compte à un autre.
-- Début de la transaction
BEGIN;
-- Étape 1 : On retire de l'argent du premier compte
UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;
-- Étape 2 : On vérifie que l'opération a marché (des lignes ont été modifiées)
IF NOT FOUND THEN
ROLLBACK; -- Annule si pas assez d'argent
RAISE EXCEPTION 'Pas assez de fonds !'; -- Erreur ! On lève une exception
END IF;
-- Étape 3 : On ajoute l'argent au deuxième compte
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;
-- On valide la transaction
COMMIT;
Ici, si le client essaie de transférer plus d'argent qu'il n'en a, la transaction est annulée et la base ne reste pas dans un état "bizarre".
Particularités et erreurs classiques
Oubli de COMMIT : si tu oublies de faire COMMIT à la fin, la base "attend" et les changements ne sont pas sauvegardés.
Oubli de WHERE : faire un update ou delete sans condition peut être catastrophique. Par exemple, DELETE FROM students sans WHERE supprime tous les étudiants.
Transactions trop longues : si une transaction reste ouverte trop longtemps, elle peut bloquer l'accès aux données et causer des soucis de perf. Termine toujours tes transactions (COMMIT ou ROLLBACK) le plus vite possible.
Les transactions, c'est ton meilleur pote pour garantir l'intégrité des données. Elles t'aident à éviter l'incohérence, surtout dans les scénarios compliqués comme l'inscription d'utilisateurs, la gestion des paiements ou la mise à jour de tables liées. Une fois que tu maîtrises les commandes BEGIN, COMMIT, ROLLBACK et SAVEPOINT, tu pourras coder des applis plus fiables et plus safe.
GO TO FULL VERSION