3.4. Transactions
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.
Note
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.