3.4. Transactions

Les transactions sont un concept fondamental de tous les systèmes de bases de données. Le point essentiel d'une transaction est qu'il assemble plusieurs étapes en une seule opération tout-ou-rien. Les états intermédiaires entre les étapes ne sont pas visibles pour les autres transactions concurrentes, et si un échec survient empêchant la transaction de bien se terminer, alors aucune des étapes n'affecte la base de données.

Par exemple, considérez la base de données d'une banque qui contiendrait la balance pour différents comptes clients, ainsi que les balances du total du dépôt par branches. Supposez que nous voulons enregistrer un virement de 100 euros du compte d'Alice vers celui de Bob. En simplifiant énormément, les commandes SQL pour ceci ressembleraient à ça

UPDATE comptes SET balance = balance - 100.00
    WHERE nom = 'Alice';
UPDATE branches SET balance = balance - 100.00
    WHERE nom = (SELECT nom_branche FROM comptes WHERE nom = 'Alice');
UPDATE comptes SET balance = balance + 100.00
    WHERE nom = 'Bob';
UPDATE branches SET balance = balance + 100.00
    WHERE nom = (SELECT nom_branche FROM comptes WHERE nom = 'Bob');

Les détails de ces commandes ne sont pas importants ici ; le point important est que cela nécessite plusieurs mises à jour séparées pour accomplir cette opération assez simple. Les employés de la banque voudront être assurés que soit toutes les commandes sont effectuées soit aucune ne l'est. Il ne serait pas acceptable que, suite à une erreur du système, Bob reçoive 100 euros qui n'ont pas été débités du compte d'Alice. De la même façon, Alice ne restera pas longtemps une cliente si elle est débitée du montant sans que celui-ci ne soit crédité sur le compte de Bob. Nous avons besoin d'une garantie comme quoi si quelque chose se passe mal, aucune des étapes déjà exécutées ne prendra effet. Grouper les mises à jour en une transaction nous donne cette garantie. Une transaction est dite atomique : du point de vue des autres transactions, cela se passe complètement ou pas du tout.

Nous voulons aussi la garantie qu'une fois une transaction terminée et validée par le système de base de données, les modifications seront enregistrées de façon permanente et ne seront pas perdues même si un arrêt brutal arrive peu après. Par exemple, si nous enregistrons un retrait d'argent par Bob, nous ne voulons surtout pas que le débit de son compte disparaisse lors d'un crash à sa sortie de la banque. Une base de données transactionnelle garantit que toutes les mises à jour faites lors d'une transaction sont enregistrées dans un stockage permanent (c'est-à-dire sur disque) avant que la transaction ne soit validée.

Une autre propriété importante des bases de données transactionnelles est en relation étroite avec la notion de mises à jour atomiques : quand de multiples transactions sont lancées en parallèle, chacune d'entre elles ne doit pas être capable de voir les modifications incomplètes faites par les autres. Par exemple, si une transaction est occupée à calculer le total de toutes les branches, il ne serait pas bon d'inclure le débit de la branche d'Alice sans le crédit de la branche de Bob, ou vice-versa. Donc, les transactions doivent être tout-ou-rien non seulement pour leur effet permanent sur la base de données, mais aussi pour leur visibilité au moment de leur exécution. Les mises à jour faites ainsi par une transaction ouverte sont invisibles aux autres transactions jusqu'à la fin de celle-ci, moment qui rendra visible toutes les mises à jours simultanément.

Avec PostgreSQL, une transaction est réalisée en entourant les commandes SQL de la transaction avec les commandes BEGIN et COMMIT. Donc, notre transaction pour la banque ressemblera à ceci

BEGIN;
UPDATE comptes SET balance = balance - 100.00
    WHERE nom = 'Alice';
-- etc etc
COMMIT;

Si, au cours de la transaction, nous décidons que nous ne voulons pas valider (peut-être nous sommes-nous aperçu que la balance d'Alice devenait négative), nous pouvons envoyer la commande ROLLBACK au lieu de COMMIT, et toutes nos mises à jour jusqu'à maintenant seront annulées.

En fait, PostgreSQL traite chaque instruction SQL comme étant exécutée dans une transaction. Si vous ne lancez pas une commande BEGIN, alors chaque instruction individuelle se trouve enveloppée avec un BEGIN et (en cas de succès) un COMMIT implicites. Un groupe d'instructions entouré par un BEGIN et un COMMIT est quelque fois appelé un bloc transactionnel.

Note : Quelques bibliothèques clients lancent les commandes BEGIN et COMMIT automatiquement, de façon à ce que vous bénéficiiez des effets des blocs transactionnels sans les demander. Vérifiez la documentation de l'interface que vous utilisez.

Il est possible de contrôler les instructions dans une transaction d'une façon plus granulaire avec l'utilisation des points de sauvegarde. Les points de sauvegarde vous permettent d'annuler des parties de la transaction tout en validant le reste. Après avoir défini un point de sauvegarde avec SAVEPOINT, vous pouvez, si nécessaire, annuler jusqu'au point de sauvegarde avec ROLLBACK TO. Toutes les modifications de la transaction dans la base de données entre le moment où le point de sauvegarde est défini et celui où l'annulation est demandée sont annulées mais les modifications antérieures au point de sauvegarde sont conservées.

Après avoir annulé jusqu'à un point de sauvegarde, il reste défini, donc vous pouvez de nouveau annuler plusieurs fois et rester au même point. Par contre, si vous êtes sûr de ne plus avoir besoin d'annuler jusqu'à un point de sauvegarde particulier, il peut être libéré pour que le système puisse récupérer quelques ressources. Gardez à l'esprit que libérer un point de sauvegarde ou annuler les opérations jusqu'à ce point de sauvegarde libérera tous les points de sauvegarde définis après lui.

Tout ceci survient à l'intérieur du bloc de transaction, donc ce n'est pas visible par les autres sessions de la base de données. Quand et si vous validez le bloc de transaction, les actions validées deviennent visibles en un seul coup aux autres sessions, alors que les actions annulées ne deviendront jamais visibles.

Rappelez-vous la base de données de la banque. Supposons que nous débitons le compte d'Alice de $100.00, somme que nous créditons au compte de Bob, pour trouver plus tard que nous aurions dû créditer le compte de Wally. Nous pouvons le faire en utilisant des points de sauvegarde comme ceci :

BEGIN;
UPDATE comptes SET balance = balance - 100.00
    WHERE nom = 'Alice';
SAVEPOINT mon_pointdesauvegarde;
UPDATE comptes SET balance = balance + 100.00
    WHERE nom = 'Bob';
-- oups ... oublions ça et créditons le compte de Wally
ROLLBACK TO mon_pointdesauvegarde;
UPDATE comptes SET balance = balance + 100.00
    WHERE nom = 'Wally';
COMMIT;

Cet exemple est bien sûr très simplifié mais il y a beaucoup de contrôle possible dans un bloc de transaction grâce à l'utilisation des points de sauvegarde. De plus, ROLLBACK TO est le seul moyen pour regagner le contrôle d'un bloc de transaction qui a été placé dans un état d'annulation par le système à cause d'une erreur, plutôt que de tout annuler et de tout recommencer.