Dans les contextes multi-utilisateurs inhérents aux SGBD, développer des techniques performantes pour contrôler les accès partagés aux données de la base - en écriture comme en lecture - et pour assurer la persistance des écritures validées est indispensable.
Dans la première partie de ce chapitre, nous revenons d'abord sur la notion de transaction, nous présentons ensuite quelques exemples d'incohérences possibles en cas d'absence de contrôle de concurrence, puis nous exposons les différents mécanismes utilisés pour assurer le contrôle de concurrence dans un SGBD.
Dans la seconde nous voyons comment garantir l'intégrité physique des données validées en assurant une reprise correcte dans tous les cas de panne du système.
Enfin, nous évoquons rapidement,
dans une troisième partie, les indicateurs standards utilisés
pour la performance de la gestion des transactions et leur mesure sur bancs
d'essai ("benchmarks").
Une transaction par définition est une séquence d'actions
- c'est-à-dire de requêtes consultatives ou modificatives - sur
les données de la BD, demandées par un même utilisateur
et considérées par lui comme un tout indivisible. Cette séquence
est à effectuer totalement ou pas du tout et, prenant la BD dans un état
cohérent, elle doit la "rendre" dans un (nouvel) état cohérent.
Ces propriétés d'atomicité et de consistance
distinguent les transactions de n'importe quelle séquence d'actions
- éventuellement réduite à une seule - sur la BD.
Une transaction a donc nécessairement un auteur, une marque de début "begin of transaction" (BOT) et une marque de fin "end of transaction" (EOT). Sa fin peut être négative - "annulation", "abandon", "abort" ou "rollback" - ou positive - "validation", "confirmation", "engagement" ou "commit", le choix du mot étant en général indifférent - le langage SQL n'en a retenu que deux COMMIT et ROLLBACK, mais peut laisser deviner que les points de vue de l'utilisateur et du SGBD sont différents.
Un utilisateur, au vu de considérations
extérieures à la BD, décide finalement de confirmer/valider
sa transaction, ce qui correspond toujours peu ou prou à un engagement
signé et daté, ou au contraire de l'abandonner/annuler, ce qui
correspond à son droit de regret . Le SGBD, pour sa part,
peut ou non mener la séquence d'actions jusqu'au terme choisi par l'utilisateur.
Il peut, soit en cours, soit au terme, être contraint de "défaire
" ce qu'il a déjà fait pour le compte de cette transaction,
quitte à refaire automatiquement ensuite. Une concurrence d'accès
insoluble ou une panne peut en être la cause. Normalement, après
une validation - COMMIT - de l'utilisateur, le SGBD doit garantir la propagation
et la persistance des modifications demandées par la transaction. C'est
la propriété de persistance ou durabilité des
transactions.
Comment le SGBD peut-il servir plusieurs utilisateurs ou, plutôt, plusieurs
transactions simultanément?
Si ces transactions, a priori, ne concernent que des données logiquement différentes, le SGBD, qui dispose des ressources nécessaires et les gère correctement, peut "se couper en quatre" - ou plus! - pour les traiter. Il ordonnance de fait son travail pour donner l'apparence extérieure d'une exécution en parallèle. Par contre si les transactions sont logiquement en concurrence sur une ou plusieurs données communes (une ligne comptable, une place d'avion, etc...) le SGBD devra gérer une ou plusieurs files d'attente c'est à dire des exécutions en séries ordonnées. L'absence de contrôle de ces concurrences logiques conduirait à des interférences plus ou moins inacceptables.
VIII.2.1.1 entre écrivains
- "lost update" (ou perte d'opération)
- "dirty read" (ou lecture inconsistante)
Obliger les transactions concurrentes à s'exécuter en succession
totale est assurément la plus forte isolation possible, mais aussi
la plus pénalisante en temps de réponse car les temps morts et
les actions sans concurrence logique, inclus dans ces transactions, sont inutilement
mis en série. Il est donc intéressant de considérer la
concurrence entre transactions au plus bas niveau possible, celui des actions
qui les composent. Dans une même transaction certaines actions sont permutables
si l'exécution des permutations donne le même résultat final.
Certaines transactions sont compatibles si elles ne sont pas en concurrence
logique. Une transaction T1 est dite précédente logique d'une
transaction T2 si une action de T1 non permutable avec une action de T2 se présente
la première . Si plusieurs transactions T1,T2, .., Tn doivent être
considérées simultanément, le problème de précédence
s'exprime par un graphe. Notons Ai1, Ai2,..,Aip les actions successives de la
transaction Ti. Une exécution entrelacée sur le modèle:
VIII.2.2.2 autres niveaux
Le standard SQL92 - alias SQL2 - prévoit une instruction SET TRANSACTION
ISOLATION LEVEL <niveau> pour le niveau SERIALIZABLE défini ci-dessus,
mais aussi pour trois autres niveaux d'isolation de plus en plus dégradés
par rapport à celui-ci:
Le gestionnaire de transaction enchaine deux fonctions: (1) il découpe les transactions en actions élémentaires au niveau physique où est gérée la concurrence, puis (2) il ordonnance ces actions. Cet ordonnancement nécessite le calcul et le maintien d'un graphe de précédences a priori et/ou d'un graphe d'attentes a posteriori, puis le choix des séquences d'exécution selon le niveau d'isolation demandé et enfin la supervision de l'exécution. Un pré-processeur alimente l'ordonnanceur en flux parallèles d'actions. Ce dernier alimente le gestionnaire de données action par action et rend compte au pré-processeur de l'état instantané de chaque transaction: encours, suspendue, rejetée, validée.
Figure 8.1 Principe de gestion des transactions concurrentes.
Dans un bon SGBD deux types de verrous peuvent être posés sur les granules de concurrence, pages ou tuples: le verrou exclusif - eXclusive lock - et le verrou partageable - Shared lock. Le premier autorise la transaction qui l'a posé sur un granule à continuer mais met en attente toute autre transaction qui voudrait ensuite lire ou écrire sur ce granule. Le second autorise par contre les lectures concurrentes. L'ordonnanceur a la charge de transformer les requêtes READ et WRITE qu'il reçoit du préprocesseur en requêtes SREAD ou XREAD et XWRITE, puis d'ajouter des requêtes SRELEASE ou XRELEASE selon les protocoles suivants:
Verrouillage en mode X (exclusif):
Figure 8.2 "s'effacer devant l'ancien"
Le mécanisme de gestion des concurrences par verrouillage à deux phases détecte a posteriori des attentes entre transactions. Des mécanismes de prévention basés sur un ordonnancement a priori des actions composantes des transactions peuvent être aussi envisagés.
L'estampillage est un mécanisme
du type prévention. Les transactions T reçoivent de l'ordonnanceur
à leur début - Begin Of Transaction - une estampille qui est
un numéro d'ordre de présentation en entrée de l'ordonnanceur.
Chaque granule G de données note l'estampille de la dernière
transaction qui y a accédé. Un granule n'accepte un nouvel accès
que s'il est demandé par une transaction "plus jeune" - c'est-à-dire
de numéro d'estampille plus grand.
Un tel algorithme d'ordonnancement total conduit souvent à de nombreuses reprises en cascades, parfois par excès de prudence. Aussi a-t-on conçu des algorithme d'ordonnancement partiel qui distinguent pour chaque granule un compteur pour les estampilles des transactions accédant en lecture et un compteur pour les estampilles des transactions accédant en écriture, ou mieux qui distinguent des versions et les couples d'estampilles correspondantes pour chaque granule. S'ils améliorent clairement le débit transactionnel leur coût en écritures et en place mémoire n'est toutefois pas négligeable.
VIII.3.2.1 Mémoires "sûres" et mémoires "fiables" (ou
"à haute disponibilité")
Les mémoires secondaires fiables les plus répandues aujourd'hui utilisent la technologie RAID: Redundant Array of Inexpensive Disks.
Logiquement "faire" l'exécution
d'une transaction, comme d'un flux de transactions, c'est lire et écrire
des données dans le cache prévu à cet effet dans la mémoire
principale puis écrire sur le ou les journaux en mémoire sûre
secondaire. Quelque soient les stratégies de transfert des pages de
données de la mémoire principale vers la mémoire secondaire
(écriture laissée à l'initiative du gestionnaire de cache,
écriture forcée au commit, écriture différée
jusqu'au prochain point de reprise automatique - en général
déclenchée par le constat d'un journal assez long) deux règles
doivent être respectées:
DÉFAIRE toujours possible:
les IMAGES AVANT mise à jour doivent être inscrites dans le
journal correspondant AVANT que les IMAGES APRÈS soient transférées
dans la base de données.
Elle sont logiquement quatre, mais les trois premières seulement sont pratiquement envisageables, selon qu'après une panne REFAIRE et DÉFAIRE sont nécessaires, REFAIRE seul ou DÉFAIRE seul est nécessaire, ni l'un ni l'autre ne sont nécessaires.
Considérons d'abord le cas où le dernier point de reprise correspond à un point mort hors transactions. Une panne non catastrophique de media est reprise à froid par rechargement de la dernière sauvegarde et déroulement du journal des IMAGES APRÈS - ou du REDO log - jusqu'au dernier point de reprise. Ensuite on est dans le même cas qu'une reprise à chaud qui correspondrait à une panne système intervenue après ce point. Il y a deux catégories de transactions à considérer alors, toutes ayant commencé après le point de reprise: celles qui avaient été validées avant, celles qui n'étaient pas encore validées. Comme en général on ne sait pas quelles pages mises à jour le gestionnaire de cache a ou n'a pas déjà transféré sur la base en mémoire secondaire, il faut d'abord DÉFAIRE les transactions non validées - en remontant le journal des IMAGES AVANT - jusqu'à leur début BOT, puis REFAIRE les transactions validées - en redescendant le journal des IMAGES APRÈS - jusqu'à leur fin EOT.
Considérons maintenant le
cas où le dernier point de reprise avait eu lieu alors que des transactions
étaient en cours: la méthode de reprise à chaud ne diffère
que par l'étendue de la relecture des journaux car il faut remonter
au début BOT de la plus ancienne transaction interrompue par la panne
ou commise après le point de reprise mais avant la panne, ce qui pouvait
être bien avant le point de reprise; la reprise à froid elle
devra cette fois-ci nécessairement être suivie d'une reprise
à chaud. Ainsi, pour garantir un redémarrage depuis une base
cohérente, quelque ait pu être la cause de l'arrêt, tous
les SGBD font toujours une reprise à chaud après le "startup".
La question des performances transactionnelles a été déjà
un peu évoquée ci-dessus en termes de temps de réponse
et de "débit" de transactions. Définissons un peu plus précisément
les grandeurs à mesurer:
- TR = temps de réponse moyen par transaction: supposons que la transaction est préparée par remplissage d'un écran qui est transmis en bloc au système, le cycle vécu par chaque utilisateur est un alternat: TI = utilisateur actif et système inactif, TR = utilisateur inactif et système actif. C'est ce deuxième qui constitue le temps réponse du système. La somme TI + TR représente un cycle transactionnel complet.
- TPS = débit de transaction (par unité de temps): si l'on considère le flux global de toutes les transactions, quelque soit l'utilisateur responsable, on peut en mesurer le débit à travers le système par unité de temps - généralement la seconde, d'où le nom Transaction(s)_Par_Seconde. Ce débit est d'autant plus grand que le "taux d'arrivée" est grand et le "temps de service" court.
Le point de vue du constructeur est toujours interne au système et analytique du fonctionnement de chacun de ses composants. Pour lui, la performance globale n'est que le résultat de la performance combinée des sous-systèmes de gestion des mémoires, des reprises, d'ordonnancement des transactions, d'optimisation des plans d'exécution des requêtes et des "piles" de protocoles de communication - pour ne citer que les principaux. Pour les caractériser et pouvoir les prédire en exploitation il est amené à construire des bancs de test où, tous les autres sous-systèmes ayant des performances connues par ailleurs, un sous-système est soumis à un véritable plan expérimental. Il s'agira, pour lui, plus d'identifier les coefficients de formules de coûts que de mesurer les coûts les plus avantageux pour une charge de travail idéale.
Quelque soit le point de vue, les qualités attendues des bancs pour réaliser la mesure de ces performances - les "benchmarks" - sont les mêmes:
- portabilité: on doit pouvoir l'appliquer à une grande variété de configurations - pour comparer les résultats de plates-formes différentes sous un même SGBD, de SGBD différents sur une même plate-forme.
- dimensionnabilité: on doit pouvoir l'appliquer, pour une configuration donnée, à des tailles variables de BD et avec des quantités variables de ressources CPU et/ou disques, afin de tester les linéarités performance/volume BD à ressources constantes, performance/ressources à volume BD constant, volume BD/ressources à performance constante.
- simplicité: malgré les trois précédentes exigences, le benchmark doit rester simple à comprendre - au moins pour les "spectateurs" - et pas trop coûteux à implémenter pour les participants à la compétition!
Devant la bataille des chiffres sans juge ni arbitre qui fit rage alors, la plupart des grands constructeurs - une cinquantaine à ce jour - ont préféré se réunir en "concile" pour stabiliser, dans des spécifications longuement discutées et précisées dans le détail, et finalement soumises à un vote formel en 1990, les définitions de deux benchmarks inspirés du TP1: les TPC-A et B. Ces benchmarks bien que ne représentant qu'une application très simple - un guichet automatique de banque ne faisant que des retraits ou des dépôts - ont été considérés comme des références obligées par presque tous les éditeurs de SGBD et ont eu une grande influence sur leur compétition. Les tps-A ou B, débits exprimés en transactions par seconde conformément aux spécifications des benchmarks TPC-A ou B, et les rapports prix/performance exprimés en milliers de US$ par tps-A ou B, ont fourni les chiffres de palmarès largement diffusés par la presse, bien que tout le monde s'accordait déjà pour trouver l'application "débit-crédit" comme très peu représentative des applications réelles des utilisateurs professionnels: l'accès exclusivement article par article ne donne aucun avantage aux SGBD relationnels par rapport aux SGBD réseaux ni à ces derniers par rapport aux SGF séquentiels indexés traditionnels. Ainsi ces benchmarks sont vite apparus plus dépendants d' aspects système généraux - multiplexage des terminaux pour le TPC-A, rapidité et/ou parallélisation des accès disques pour le TPC-B - que d'aspects proprement SGBD - optimisation et gestion transactionnelle des données.
C'est pourquoi le TPC a mis en chantier deux autres spécifications l'une pour le "business transaction processing" avec le TPC-C (1992), l'autre pour le "decision support" avec le TPC-D (1995). Toutes deux offrent un schéma et des requêtes plus complexes et plus représentatifs des applications réelles. Leur sémantique commune est inspirée des applications de gestion de clients, commandes et stocks. Les métriques en sont respectivement des tpm-C (transactions par minute pour le TPC-C) et des qph-D (requêtes - queries - par heure pour le TPC-D). D'autres spécifications ciblant plus particulièrement les gros serveurs BD et les configurations clients-serveurs sont en cours de discussion.
Les éditeurs de SGBD eux-mêmes,
ou des sociétés indépendantes d'ingénierie du logiciel,
proposent des interfaces dédiées aux administrateurs de SGBD qui
varient de la simple manipulation visuelle des données de la métabase
à de véritables systèmes-experts dont la base de modèles
et de règles peut être très riche - et chère!
Le concept de transaction donne aux Systèmes de Bases de Données
toute leur dimension dynamique: l'exigence de cohérence à la fin
de chaque transaction - et de consistance dans la suite des transactions - conduit
à l'exigence de services de gestion de la concurrence et de gestion des
reprises non seulement fiables mais performants.
On peut théoriquement imaginer que ces services soient assurés par des sous-systèmes différents, comme ceux de contrôles des droits sur les données, d'optimisation des requêtes et de controle d'intégrité, en amont, ainsi que celui de gestion des caches de données, en aval.
Leur implantation sur des architectures
distribuées pousse dans ce sens. Mais le besoin de performance pousse
au contraire à intégrer le plus possible ces services entre eux
et avec le gestionnaire de cache. Enormément d'efforts de conception
d'algorithmes nouveaux, de modélisation et de mesures de performances
sont encore en cours, tant du côte des chercheurs que du côté
des constructeurs, pour de nouvelles intégrations sur de nouvelles architectures.
[Gardarin 82] G. Gardarin, Bases de Données: Les Systèmes et
leurs Langages, Eyrolles, 1982
[Date 83] C.J. Date, An introduction to DataBase Systems - Volume 2 Addisson-Wesley, 1983
[Bernstein & al. 87] Ph.A. Bernstein, V. Hadzilacos and N. Goodman, Concurrency control and recovery in DataBase Systems, Addison-Wesley, 1987
[Gray 93] Jim Gray and Andreas Reuter, Transaction Processing: concepts and techniques, Morgan Kofmann, 1993
[Gray 93] Jim Gray editor, The Benchmarks HandBook for DataBases and Transaction Processing Systems - 2nd edition, Morgan Kaufmann, 1993