Un SGBD est par définition multi-utilisateurs, par conséquent l'exécution simultanée de plusieurs applications peut poser des problèmes d'accès concurrents (une même information étant manipulée par plusieurs utilisateurs à la fois). L'unité de concurrence est la transaction qui joue également un rôle vis à vis du contrôle de l'intégrité des données. Une base de données doit être cohérente avant et après une transaction. Par contre, la cohérence peut être violée pendant l'exécution d'une transaction.
Une transaction est une séquence d'opérations qui accèdent et modifient le contenu d'une base de données et qui est complètement exécutée ou pas du tout (unité atomique de traitement). Une transaction s'effectue soit complètement et alors les mises à jour qu'elle a effectuées sur la base de données sont validées, soit elle s'effectue incomplètement (annulation ou panne) et alors toutes les mises à jour effectuées depuis le début de la transaction sont invalidées.
Les propriétés d'une transaction sont les suivantes (ACID) :
On considère une transaction T1 qui consiste à annuler N réservations sur un vol V1 (X représente le nombre de places réservées sur le vol V1) et à réserver N places sur un vol V2 (Y représente le nombre de places réservées sur le vol V2). La transaction T2 réserve Mplaces sur le vol V1. L'exécution concurrente de T1 et T2 sans contraintes de synchronisation peut produire un certain nombre de problèmes dont les plus importants sont la perte d'opérations et les lectures impropres.
T1 T2lire(X)
X := X-N
lire(X)
X := X+M
écrire(X)
lire(Y)
écrire(X) --> écrire(X:=X-N) est perdu par T1
Y := Y+N
écrire(Y)
T1 T2lire(X)
X := X-N
écrire(X)
lire(X)
X := X+M
lire(Y)
annulation T1
La valeur de X lue par T2 est incohérente car X a été lu après la mise à jour faite par T1 qui a été ensuite invalidée par l'annulation de T1.
Il faut fixer une propriété déterminant une exécution correcte de transactions concurrentes. Cette propriété est la sérialisibilité à savoir une exécution concurrente de transactions est correcte si elle est équivalente (produit le même résultat) à une exécution en série. Il faut noter que cette définition n'est pas constructive, elle ne donne pas de moyens de garantir la propriété.
La sérialisation est une propriété forte qui limite le parallélisme à l'exécution donc les performances. Certains SGBD comme Oracle propose des assouplissements de cette propriété basés sur des protocoles multi-versions.Une transaction peut échouer pour de nombreuses raisons :
Cet échec de la transaction peut entrainer une incohérence si une partie seulement des mises à jour ont été effectuées (ne respecte pas la propriété d'atomicité). Par conséquent, il faut restituer l'état antérieur de la base grace à un mécanisme de reprise.
La notion de transaction sert de granule de contrôle de la concurrence et permet également de faire des reprises sur erreurs logicielles (la base se trouve dans un état cohérent à chaque point observable). Ce mécanisme est appelé "reprise à chaud".La vie d'une transaction dans le système peut se décrire de la manière suivante :
Les pannes matérielles ou logicielles graves ne peuvent se traiter par un simple mécanisme transactionnel. Le principe général est de garder un historique persistant de tout ce qui se passe sur la base de données. A partir de cet historique (appelé journal) et d'états antérieurs (sauvegardes) de la base de données, on peut reconstituer la base de données dans l'état cohérent qui précède la panne. Ce mécanisme est appelé "reprise à froid".
Le journal est donc un support sur disque qui maintient les informations nécessaires pour la reprise :
Un point de reprise consiste à écrire sur disque les mises à jour des transactions validées (copie du buffer sur le disque). Ce mécanisme s'effectue périodiquement, toutes les N secondes, toutes les N transactions, ... Le journal est également sauvegardé sur disque à ce moment là.
On peut distinguer 2 grandes techniques pour assurer la sérialisibilité :
De manière industrielle, la seule solution implantée est l'appoche par verrouillage.
Un verrou est une variable d'état associée à un objet X de la base et indiquant son état vis à vis des opérations de lecture / écriture.
Un verrou peut être binaire (deux états verrouillé ou libre avec deux opérations verrouiller(X) et libérer(X)) ou ternaire. Un verrou binaire est trop restrictif puisqu'un objet est bloqué pour toute opération que l'on veut effectuer sur lui, ce qui implique qu'on ne peut faire des lectures simultanées.
Un verrou ternaire permet 3 états, en lecture (ou partagé), en écriture, ou libre. Le verrou en écriture n'est alloué que si l'objet est libre, c'est à dire si aucune opération ni de lecture, ni d'écriture ne s'effectue sur cet objet. Le verrou en lecture est alloué si l'objet est libre ou si aucune opération d'écriture ne s'effectue sur lui (par contre des opérations de lecture peuvent se dérouler simultanément, ce qui nécessite de maintenir un compteur pour connaitre le nombre de transactions "lisant" cet objet).
Un protocole de verrouillage sans aucune contrainte sur l'allocation des verrous, ne permet pas de garantir la propriété de sérialisabilité. De nombreux protocoles ont été proposés, le plus classique étant le protocole de verrouillage à deux phases.
Dans ce protocole, toutes les acquisitions de verrous sont faites avant la première libération. On dit qu'il y a une phase d'expansion suivie d'une phase de libération.
Le protocole de verrouillage à 2 phases garantit la propriété de sérialisabilité mais ne permet pas d'éviter les situations d'interblocage. Un interblocage se produit lorsque une transaction est bloquée en attente de ressources provenant d'une autre transaction (et réciproquement). Plus généralement, un interblocage peut se passer entre n transactions. Deux techniques sont possibles, une pessimiste et une optimiste :
Différentes granularités de contrôle de la concurrence sont possibles :
Plus la granularité est petite, plus le niveau de concurrence augmente. Par contre le niveau de complexité de gestion des verrous augmente et les performances baissent.
A priori le choix est dépendant du type des transactions (nature des informations manipulées). La plupart du temps dans les systèmes commerciaux la granularité est fixe (sauf indiquée explicitement). Certains systèmes font du verrouillage adaptatif en tenant compte de la hiérarchie des objets (plutot que de bloquer tous les attributs d'un n-uplet il vaut mieux bloquer le n-uplet et ainsi de suite). Dans les systèmes actuels le niveau offert est celui du n-uplet.
L'échec d'une transaction (suicide ou assassinat) entraine un état incohérent de la base de données. Il faut donc ramener la base de données dans l'état cohérent précédent le plus proche. Pour cela, un mécanisme de reprise offre deux opérations "défaire" et "refaire". Défaire une opération peut amener en à défaire d'autres. Deux techniques de reprise sont utilisées, mise à jour différée ou mise à jour immédiate.
Reprise basée sur la mise à jour différée :
La mise à jour différée consiste à ne faire les écritures effectives qu'au moment de la validation de la transaction. Les modifications sur la base de données sont enregistrées dans le journal lors de la validation. La procédure de reprise consiste alors à parcourir le journal pour refaire les opérations d'écriture des transactions validées depuis le dernier point de reprise (dans l'ordre d'apparition du journal).
Par contre, il n'y a rien à faire pour les transactions non validées (la base de données n'a pas été mise à jour).
Reprise basée sur la mise à jour immédiate :Les écritures sont effectuées sur la base de données immédiatement et les anciennes valeurs des informations modifiées sont enregistrées dans le journal. La procédure de reprise consiste alors à :