Les transactions sont un concept fondamental de tous les systèmes de bases de données. Une transaction assemble plusieurs étapes en une seule opération tout-ou-rien. Les états intermédiaires entre les étapes ne sont pas visibles par les transactions concurrentes. De plus, si un échec survient qui empêche le succès de la transaction, alors aucune des étapes n'affecte la base de données.
Si l'on considère, par exemple, la base de données d'une banque qui contient le solde de différents comptes clients et le solde total des dépôts par branches et que l'on veuille enregistrer un virement de 100 euros du compte d'Alice vers celui de Bob, les commandes SQL peuvent ressembler à cela (après simplification) :
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');
Ce ne sont pas les détails des commandes qui importent ici ; le point important est la nécessité de plusieurs mises à jour séparées pour accomplir cette opération assez simple. Les employés de la banque veulent être assurés que, soit toutes les commandes sont effectuées, soit aucune ne l'est. Il n'est pas envisageable 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 fidèle si elle est débitée du montant sans que celui-ci ne soit crédité sur le compte de Bob.
Il est important de garantir que si quelque chose se passe mal, aucune des étapes déjà exécutées n'est prise en compte. Le regroupement des mises à jour au sein d'une transaction apporte cette garantie. Une transaction est dite atomique : du point de vue des autres transactions, elle passe complètement ou pas du tout.
Il est également nécessaire de garantir qu'une fois la transaction terminée et validée par la base de données, les transactions sont enregistrées définitivement et ne peuvent être perdues, même si une panne survient peu après.
Ainsi, si un retrait d'argent est effectué par Bob, il ne faut absolument pas que le débit de son compte disparaisse avec l'apparition d'une panne juste après son départ de la banque.
Une base de données transactionnelle garantit que toutes les mises à jour faites lors d'une transaction sont stockées de manière persistante (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 plusieurs transactions sont lancées en parallèle, aucune d'entre elles ne doit être capable de voir les modifications incomplètes effectuées par les autres.
Ainsi, si une transaction calcule le total de toutes les branches, inclure le débit de la branche d'Alice sans le crédit de la branche de Bob, ou vice-versa, est une réelle erreur.
Les transactions doivent donc être tout-ou-rien, non seulement pour leur effet persistent sur la base de données, mais aussi pour leur visibilité au moment de leur exécution. Les mises à jour faites jusque-là par une transaction ouverte sont invisibles aux autres transactions jusqu'à la fin de celle-là. À ce moment toutes les mises à jours deviennent simultanément visibles.
Sous PostgreSQL™, une transaction est déclarée en entourant les commandes SQL de la transaction par les commandes BEGIN et COMMIT .
La transaction bancaire ressemble alors à ceci :
BEGIN; UPDATE comptes SET balance = balance - 100.00 WHERE nom = 'Alice'; -- etc etc COMMIT;
Si, au cours de la transaction, il est décidé de ne pas valider (peut-être la banque s'aperçoit-elle que la balance d'Alice passe en négatif), la commande ROLLBACK peut être utilisée à la place de COMMIT . Toutes les mises à jour réalisées jusque-là sont alors annulées.
En fait, PostgreSQL™ traite chaque instruction SQL comme si elle était exécutée dans une transaction. En l'absence de commande BEGIN explicite, chaque instruction individuelle se trouve implicitement entourée d'un BEGIN et (en cas de succès) d'un COMMIT .
Un groupe d'instructions entourées par BEGIN et COMMIT est parfois appelé bloc transactionnel.
Quelques bibliothèques clientes lancent les commandes BEGIN et COMMIT automatiquement. L'utilisateur bénéficie alors des effets des blocs transactionnels sans les demander. Ces informations se trouvent en général dans la documentation de l'interface utilisée.
Il est possible d'augmenter la granularité de contrôle des instructions au sein d'une transaction en utilisant des points de retournement ( savepoint ). Ceux-ci permettent d'annuler des parties de la transaction tout en validant le reste.
Après avoir défini un point de retournement à l'aide de SAVEPOINT , les instructions exécutées depuis ce point peuvent, au besoin, être annulées avec ROLLBACK TO . Toutes les modifications de la base de données effectuées par la transaction entre le moment où le point de retournement a été défini et celui où l'annulation est demandée sont annulées mais les modifications antérieures à ce point sont conservées.
Le retour à un point de retournement ne l'annule pas. Il reste défini et peut donc être utilisé plusieurs fois. À l'inverse, lorsqu'il n'est plus nécessaire de revenir à un point de sauvegarde pariculier, il peut être relâché, ce qui permet de libérer des ressources systèmes. Il faut savoir toutefois que relâcher un point de retournement, ou y revenir relâche tous les points de sauvegarde qui ont été définis après.
Tout ceci survient à l'intérieur du bloc de transaction, et n'est donc pas visible par les autres sessions en cours sur la base de données. Si le bloc est validé, et à ce moment-là seulement, toutes les actions validées deviennent immédiatement visibles par les autres sessions, tandis que les actions annulées ne le seront jamais.
Reconsidérant la base de données de la banque, on peut supposer vouloir débiter le compte d'Alice de $100.00, somme à créditer sur le compte de Bob, mais considérer plus tard que c'est le compte de Wally qu'il convient de créditer. À l'aide des points de retournement, cela peut-être réalisé ainsi :
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 de nombreux contrôles sont réalisables au sein d'un bloc de transaction grâce à l'utilisation des points de retournement. Qui plus est, ROLLBACK TO est le seul moyen de regagner le contrôle d'un bloc de transaction placé dans un état d'annulation par le système du fait d'une erreur. C'est plus rapide que de tout annuler pour tout recommencer.