Parfois, les transactions me font penser à des persos de films de super-héros. Elles sauvent nos bases de données du désastre en cas de plantage, d'erreur ou de pépin. Si tu bosses sur une tâche qui demande plusieurs opérations inséparables, les transactions garantissent que tout se fait d'un seul bloc. On va voir comment ça marche avec un exemple de gestion de paiement.
Gestion des paiements
Imagine la situation classique : t'as deux comptes bancaires, et tu veux transférer de l'argent de l'un à l'autre. Ce n'est pas juste une opération "un bouton". Il faut s'assurer qu'on retire bien l'argent d'un compte et qu'on l'ajoute à l'autre. La moindre erreur peut être catastrophique : soit les deux comptes restent inchangés, soit le solde est faussé (genre, l'argent disparaît ou apparaît "par magie").
Scénario : Transfert d'argent entre comptes
Voici notre code. Lis-le attentivement, comme si c'était un message venu d'une galaxie lointaine :
-- On démarre la transaction
BEGIN;
-- Étape 1. On retire l'argent du compte de l'expéditeur
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
-- Étape 2. On ajoute l'argent au compte du destinataire
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- Tout s'est bien passé ? Alors on valide les changements !
COMMIT;
Qu'est-ce qui est important ici ?
- Si à
Étape 1ouÉtape 2y'a un souci (genre une erreur dans la requête, pas assez d'argent), la transaction peut être annulée avecROLLBACK, et les données restent comme avant. COMMITgarantit que les changements ne sont appliqués que si TOUTES les étapes passent sans souci.
On ajoute une vérif du solde
Et si l'expéditeur n'a pas assez d'argent pour le transfert ? On va ajouter une vérif du solde pour éviter de le mettre "dans le rouge" par accident.
-- On démarre la transaction
BEGIN;
-- On récupère le solde actuel de l'expéditeur
DO $$
DECLARE
current_balance NUMERIC;
BEGIN
SELECT balance INTO current_balance FROM accounts WHERE account_id = 1;
-- On vérifie si y'a assez d'argent
IF current_balance >= 100 THEN
-- Si c'est bon, on fait le transfert
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- On valide les changements
COMMIT;
ELSE
-- Si pas assez d'argent, on annule
ROLLBACK;
RAISE NOTICE 'Pas assez de fonds pour le transfert !';
END IF;
END $$;
Qu'est-ce qui devient intéressant ici ?
- On utilise un bloc PL/pgSQL avec une condition
IF. Si le solde est inférieur au montant, la transaction est refusée et rien ne change. ROLLBACKannule les changements s'il y en a eu (même si ici, y'a rien à annuler, mais c'est une bonne habitude).
Ce cours est dédié à des scénarios réels d'utilisation des transactions, donc j'ai décidé de te montrer un exemple de la vraie vie. Il contient une procédure stockée et est écrit en PL-SQL. Je pense que t'as déjà assez d'expérience pour piger comment ça marche. Plus tard, on reviendra sur le sujet PL-SQL et on verra des exemples encore plus costauds.
Mise à jour massive de données dans une transaction
Créer une transaction, c'est utile pas seulement pour les transferts d'argent. Par exemple, t'as une base de données d'e-commerce où chaque jour des dizaines de commandes changent de statut, genre de "en livraison" à "terminée". Comment mettre à jour plein d'enregistrements d'un coup tout en gardant la possibilité d'annuler si ça foire ? Bien sûr, on utilise une transaction.
Regardons un autre scénario : mise à jour des statuts de commandes.
Voici un exemple :
-- On démarre la transaction
BEGIN;
-- Étape 1. On met à jour les commandes dont la date de livraison est passée
UPDATE orders
SET status = 'terminée'
WHERE delivery_date < CURRENT_DATE;
-- Étape 2. On notifie que la mise à jour a réussi
RAISE NOTICE 'Tous les statuts de commandes ont été mis à jour avec succès.';
-- On valide les changements
COMMIT;
Et si ça foire ?
Y'a toujours un risque d'erreur. Par exemple, t'as oublié de mettre la condition WHERE, et maintenant toutes les commandes ont leur statut passé à terminée. Pour éviter ça, il faut toujours finir la transaction ou l'annuler explicitement.
Voyons un scénario avec annulation :
-- On démarre la transaction
BEGIN;
-- Étape 1. Tentative de mise à jour sans condition (oups, erreur !)
UPDATE orders
SET status = 'terminée';
-- Annulation de la transaction à cause de l'erreur
ROLLBACK;
-- Les commandes restent inchangées
On ajoute un peu de "flexibilité" avec SAVEPOINT
Pas toujours besoin d'annuler toute la transaction. Si ton scénario a plusieurs étapes, tu peux vouloir annuler juste une partie. C'est là que SAVEPOINT entre en jeu.
Maintenant, notre scénario : gérer plusieurs étapes avec possibilité d'annuler juste l'une d'elles.
Imagine que tu traites une commande en plusieurs étapes : retirer les produits du stock, mettre à jour le statut de la commande, envoyer une notif au client. Si la notif ne part pas, tu veux annuler juste cette étape, mais garder le reste.
-- Démarrer la transaction
BEGIN;
-- Étape 1. On retire les produits du stock
UPDATE products
SET stock = stock - 1
WHERE product_id = 101;
-- On pose un point de sauvegarde
SAVEPOINT step1;
-- Étape 2. On met à jour le statut de la commande
UPDATE orders
SET status = 'expédiée'
WHERE order_id = 202;
-- Tentative d'envoi de notif au client
SAVEPOINT step2;
-- Oups, erreur pendant la notif !
ROLLBACK TO SAVEPOINT step2;
-- On décide que c'est safe de finir la transaction
COMMIT;
Conclusion
Les transactions, ce n'est pas juste un outil technique, c'est la garantie de l'intégrité de tes données. Elles protègent contre "l'effet domino", où une erreur peut tout casser. À chaque fois que tu fais plusieurs opérations liées, demande-toi : "Et si l'une d'elles plante ?" Si la réponse c'est "catastrophe", alors il est temps d'utiliser une transaction. Retiens : mieux vaut passer quelques minutes à écrire une transaction que des heures à réparer les dégâts après un plantage. Tes utilisateurs (et tes nerfs) te diront merci !
GO TO FULL VERSION