3. Fichiers séquentiels

Il n’est jamais inutile de rappeler qu’une base de données n’est rien d’autre qu’un ensemble de données stockées sur un support persistant. La technique de très loin la plus répandue consiste à organiser le stockage des données sur un disque au moyen de fichiers. Leurs principes généraux sont décrits dans ce qui suit.

S1: Enregistrements, blocs et fichiers

Pour le système d’exploitation, un fichier est une séquence d’octets sur un disque. Les fichiers gérés par un SGBD sont un peu plus structurés. Ils sont constitués de blocs, qui eux-même contiennent des enregistrements (records en anglais), lesquels représentent physiquement les entités du SGBD. Selon le modèle logique du SGBD, ces entités peuvent être des n-uplets dans une relation, ou des objets. Nous nous limiterons au premier cas dans ce qui suit.

Important

À partir de maintenant le terme vague de “données” que nous avons utilisé jusqu’à présent désigne précisément un enregistrement. Dit autrement, les enregistrements constituent notre unité de gestion de l’information: on ne descend jamais à une granularité plus fine.

Enregistrements

Un n-uplet dans une table relationnelle est constitué d’une liste d’attributs, chacun ayant un type. À ce n-uplet est représenté physiquement, sous forme binaire, par un enregistrement, constitué de champs (field en anglais). Chaque type d’attribut détermine la taillle du champ nécessaire pour stocker une instance du type. Le Tableau 3.1 donne la taille habituelle utilisée pour les principaux types de la norme SQL, étant entendu que les systèmes sont libres de choisir le mode de stockage.

Tableau 3.1 Types SQL et tailles (en octets)
Type Taille (en octets)
SMALLINT 2
INTEGER 4
BIGINT 8
FLOAT 4
DOUBLE PRECISION 8
NUMERIC (M, D) M, D+2 si M < D
DECIMAL (M, D) M, D+1 si M < D
CHAR(M) M
VARCHAR(M) L+1, avec L \(\leq\) M
BIT VARYING \(< 2^8\)
DATE 8
TIME 6
DATETIME 14

La taille d’un enregistrement est, en première approximation, la somme des tailles des champs stockant ses attributs. En pratique les choses sont un peu plus compliquées. Les champs – et donc les enregistrements – peuvent être de taille variable par exemple. Si la taille de l’un de ces enregistrements de taille variable augmente au cours d’une mise à jour, il faut pouvoir trouver un espace libre. Se pose également la question de la représentation des valeurs à NULL. Nous discutons des principaux aspects de la représentation des enregistrements dans ce qui suit.

Champs de tailles fixe et variable

Comme l’indique le Tableau 3.1, les types de la norme SQL peuvent être divisés en deux catégories : ceux qui peuvent être représentés par un champ une taille fixe, et ceux qui sont représentés par un champ de taille variable.

Les types numériques (entiers et flottants) sont stockés au format binaire sur 2, 4 ou 8 octets. Quand on utilise un type DECIMAL pour fixer la précision, les nombres sont en revanche stockés sous la forme d’une chaîne de caractères. Par exemple un champ de type DECIMAL(12,2) sera stocké sur 12 octets, les deux derniers correspondant aux deux décimales. Chaque octet contient un caractère représentant un chiffre.

Les types DATE et TIME peuvent être simplement représentés sous la forme de chaînes de caractères, aux formats respectifs ‘AAAAMMJJ’ et ‘HHMMSS’.

Le type CHAR est particulier : il indique une chaîne de taille fixe, et un CHAR(5) sera donc stocké sur 5 octets. Se pose alors la question : comment est représentée la valeur “Bou” ? Il y a deux solutions :

  1. on complète les deux derniers caractères avec des blancs ;
  2. on complète les deux derniers caractères avec un caractère conventionnel.

La convention adoptée influe sur les comparaisons puisque dans un cas on a stocké “Bou ” (avec deux blancs), et dans l’autre “Bou” sans caractères complétant la longueur fixée. Si on utilise le type CHAR il est important d’étudier la convention adoptée par le SGBD.

On utilise beaucoup plus souvent le type VARCHAR(n) qui permet de stocker des chaînes de longueur variable. Il existe (au moins) deux possibilités :

  1. le champ est de longueur n+1, le premier octet contenant un entier indiquant la longueur exacte de la chaîne ; si on stocke “Bou” dans un VARCHAR(10), on aura un codage “3Bou”, le premier octet codant un 3 (au format binaire), les trois octets suivants des caratères ‘B’, ‘o’ et ‘u’, et les 7 octets suivants restant inutilisés ;
  2. le champ est de longueur l+1, avec l < n ; ici on ne stocke pas les octets inutilisés, ce qui permet d’économiser de l’espace.

Noter qu’en représentant un entier sur un octet, on limite la taille maximale d’un VARCHAR à 255 (vous voyez pourquoi?). Une variante qui peut lever cette limite consiste à remplacer l’octet initial contenant la taille par un caractère de terminaison de la chaîne (comme en C).

Le type BIT VARYING peut être représenté comme un VARCHAR, mais comme l’information stockée ne contient pas que des caractères codés en ASCII, on ne peut pas utiliser de caractère de terminaison puisqu’on ne saurait pas le distinguer des caractères de la valeur stockée. On préfixe donc le champ par la taille utile, sur 2, 4 ou 8 octets selon la taille maximale autorisé pour ce type.

On peut utiliser un stockage optimisé dans le cas d’un type énuméré dont les instances ne peuvent prendre leur (unique) valeur que dans un ensemble explicitement spécifié (par exemple avec une clause CHECK). Prenons l’exemple de l’ensemble de valeurs suivant 

valeur1, valeur2, ..., valeurN

Le SGBD doit contrôler, au moment de l’affectation d’une valeur à un attribut de ce type, qu’elle appartient bien à l’ensemble énuméré {valeur1, valeur2, ..., valeurN}. On peut alors stocker l’indice de la valeur, sur 1 ou 2 octets selon la taille de l’ensemble énuméré (au maximum 65535 valeurs pour 2 octets). Cela représente un gain d’espace, notamment si les valeurs consistent en chaînes de caractères.

En-tête d’enregistrement

De même que l’on préfixe un champ de longueur variable par sa taille utile, il est souvent nécessaire de stocker quelques informations complémentaires sur un enregistrement dans un en-tête. Ces informations peuvent être ;

  • la taille de l’enregistrement, s’il est de taille variable ;
  • un pointeur vers le schéma de la table, pour savoir quel est le type de l’enregistrement ;
  • la date de dernière mise à jour ;
  • etc.

On peut également utiliser cet en-tête pour les valeurs NULL. L’absence de valeur pour un des attributs est en effet délicate à gérer : si on ne stocke rien, on risque de perturber le découpage du champ, tandis que si on stocke une valeur conventionnelle, on perd de l’espace. Une solution possible consiste à créer un masque de bits, un pour chaque champ de l’enregistrement, et à donner à chaque bit la valeur 0 si le champ est NULL, et 1 sinon. Ce masque peut être stocké dans l’en-tête de l’enregistrement, et on peut alors se permettre de ne pas utiliser d’espace pour une valeur NULL, tout en restant en mesure de décoder correctement la chaîne d’octets constituant l’enregistrement.

Exemple

Prenons l’exemple d’une table Film avec les attributs id de type INTEGER, titre de type VARCHAR(50) et annee de type INTEGER. Regardons la représentation de l’enregistrement (123, 'Vertigo', NULL) (donc l’année est inconnue).

L’identifiant est stocké sur 4 octets, et le titre sur 8 octets, dont un pour la longueur. L’en-tête de l’enregistrement contient un pointeur vers le schéma de la table, sa longueur totale (soit 4 + 8), et un masque de bits 110 indiquant que le troisième champ est à NULL. La Fig. 3.1 montre cet enregistrement : notez qu’en lisant l’en-tête, on sait calculer l’adresse de l’enregistrement suivant.

_images/exenr.png

Fig. 3.1 Représentation d’un enregistrement

Blocs

Le stockage des enregistrements dans un fichier doit tenir compte du découpage en blocs de ce fichier. En général il est possible de placer plusieurs enregistrements dans un bloc, et on veut éviter qu’un enregistrement chevauche deux blocs. Le nombre maximal d’enregistrements de taille E pour un bloc de taille B est donné par \(\lfloor B/E \rfloor\) où la notation \(\lfloor x \rfloor\) désigne le plus grand entier inférieur à x.

Prenons l’exemple d’un fichier stockant une table qui ne contient pas d’attributs de longueur variable – en d’autres termes, elle n’utilise pas les types VARCHAR ou BIT VARYING. Les enregistrements ont alors une taille fixe obtenue en effectuant la somme des tailles de chaque attribut. Supposons que cette taille soit en l’occurrence 84 octets, et que la taille de bloc soit 4096 octets. On va de plus considérer que chaque bloc contient un en-tête de 100 octets pour stocker des informations comme l’espace libre disponible dans le bloc, un chaînage avec d’autres blocs, etc. On peut donc placer

\[\lfloor \frac{4096 - 100}{84} \rfloor = 47\]

enregistrements dans un bloc. Notons qu’il reste dans chaque bloc \(3996 - (47 \times 84) = 48\) octets inutilisés dans chaque bloc.

_images/blocenr1.png

Fig. 3.2 Stockage des enregistrements dans un bloc

Le transfert en mémoire de l’enregistrement 563 de ce fichier est simplement effectué en déterminant dans quel bloc il se trouve (soit \(\lfloor 563/47 \rfloor + 1 = 12\)), en chargeant le douzième bloc en mémoire centrale et en prenant dans ce bloc l’enregistrement. Le premier enregistrement du bloc 12 a le numéro \(11 \times 47 + 1 = 517\), et le dernier enregistrement le numéro \(12 \times 47 = 564\). L’enregistrement 563 est donc l’avant-dernier du bloc, avec pour numéro interne le 46 (voir Fig. 3.2).

Le petit calcul qui précède montre comment on peut localiser physiquement un enregistrement : par son fichier, puis par le bloc, puis par la position dans le bloc. En supposant que le fichier est codé par ‘F1’, l’adresse de l’enregistrement peut être représentée par ‘F1.12.46’.

Il y a beaucoup d’autres modes d’adressage possibles. L’inconvénient d’utiliser une adresse physique par exemple est que l’on ne peut pas changer un enregistrement de place sans rendre du même coup invalides les pointeurs sur cet enregistrement (dans les index par exemple).

Pour permettre le déplacement des enregistrements on peut combiner une adresse logique qui identifie un enregistrement indépendamment de sa localisation. Une table de correspondance permet de gérer l’association entre l’adresse physique et l’adresse logique (voir Fig. 3.3). Ce mécanisme d’indirection permet beaucoup de souplesse dans l’organisation et la réorganisation d’une base puisqu’il il suffit de référencer systématiquement un enregistrement par son adresse logique, et de modifier l’adresse physique dans la table quand un déplacement est effectué. En revanche il entraîne un coût additionnel puisqu’il faut systématiquement inspecter la table de correspondance pour accéder aux données.

_images/blocenr2.png

Fig. 3.3 Adressage avec indirection

Une solution intermédiaire combine adressages physique et logique. Pour localiser un enregistrement on donne l’adresse physique de son bloc, puis, dans le bloc lui-même, on gère une table donnant la localisation au sein du bloc ou, éventuellement, dans un autre bloc.

Reprenons l’exemple de l’enregistrement F1.12.46. Ici F1.12 indique bien le bloc 12 du fichier F1. En revanche 46 est une identification logique de l’enregistrement, gérée au sein du bloc. La Fig. 3.4 montre cet adressage à deux niveaux : dans le bloc F1.12, l’enregistrement 46 correspond à un emplacement au sein du bloc, tandis que l’enregistrement 57 a été déplacé dans un autre bloc.

_images/blocenr3.png

Fig. 3.4 Combinaison adresse logique/adresse physique

Noter que l’espace libre dans le bloc est situé entre l’en-tête du bloc et les enregistrements eux-mêmes. Cela permet d’augmenter simultanément ces deux composantes au moment d’une insertion par exemple, sans avoir à effectuer de réorganisation interne du bloc.

Ce mode d’identification offre beaucoup d’avantages, et est utilisé par ORACLE par exemple. Il permet de réorganiser souplement l’espace interne à un bloc.

Enregistrements de taille variable

Une table qui contient des attributs VARCHAR ou BIT VARYING est représentée par des enregistrements de taille variable. Quand un enregistrement est inséré dans le fichier, on calcule sa taille non pas d’après le type des attributs, mais d’après le nombre réel d’octets nécessaires pour représenter les valeurs des attributs. Cette taille doit de plus être stockée au début de l’emplacement pour que le SGBD puisse déterminer le début de l’enregistrement suivant.

Il peut arriver que l’enregistrement soit mis à jour, soit pour compléter la valeur d’un attribut, soit pour donner une valeur à un attribut qui était initialement à NULL. Dans un tel cas il est possible que la place initialement réservée soit insuffisante pour contenir les nouvelles informations qui doivent être stockées dans un autre emplacement du même fichier. Il faut alors créer un chaînage entre l’enregistrement initial et les parties complémentaires qui ont dû être créées.

_images/blocenr4.png

Fig. 3.5 Mises à jour d’un enregistrement de taille variable

Considérons par exemple le scénario suivant, illustré dans la Fig. 3.5 :

  • on insère dans la table Film un film Marnie, sans résumé ; l’enregistrement correspondant est stocké dans le bloc F1.12, et prend le numéro 46 ;
  • on insère un autre film, stocké à l’emplacement 47 du bloc F1.12 ;
  • on s’aperçoit alors que le titre exact est Pas de printemps pour Marnie, ce qui peut se corriger avec un ordre UPDATE : si l’espace libre restant dans le bloc est suffisant, il suffit d’effectuer une réorganisation interne pendant que le bloc est en mémoire centrale, réorganisation qui a un coût nul en terme d’entrées/sorties ;
  • enfin on met à nouveau l’enregistrement à jour pour stocker le résumé qui était resté à NULL : cette fois il ne reste plus assez de place libre dans le bloc, et l’enregistrement doit être déplacé dans un autre bloc, tout en gardant la même adresse.

Au lieu de déplacer l’enregistrement entièrement (solution adoptée par Oracle par exemple), on pourrait le fragmenter en stockant le résumé dans un autre bloc, avec un chaînage au niveau de l’enregistrement (solution adoptée par MySQL). Le déplacement (ou la fragmentation) des enregistrements de taille variable est évidemment pénalisante pour les performances. Il faut effectuer autant de lectures sur le disque qu’il y a d’indirections (ou de fragments), et on peut donc assimiler le coût d’une lecture d’un enregistrement en n parties, à n fois le coût d’un enregistrement compact. Un SGBD comme Oracle permet de réserver un espace disponible dans chaque bloc pour l’agrandissement des enregistrements afin d’éviter de telles réorganisations.

Les enregistrements de taille variable sont un peu plus compliqués à gérer pour le SGBD que ceux de taille fixe. Les modules accédant au fichier doivent prendre en compte les en-têtes de bloc ou d’enregistrement pour savoir où commence et où finit un enregistrement donné.

En contrepartie, un fichier contenant des enregistrements de taille variable utilise souvent mieux l’espace qui lui est attribué. Si on définissait par exemple le titre d’un film et les autres attributs de taille variable comme des CHAR et pas comme des VARCHAR, tous les enregistrements seraient de taille fixe, au prix de beaucoup d’espace perdu puisque la taille choisie correspond souvent à des cas extrêmes rarement rencontrés – un titre de film va rarement jusqu’à 50 octets.

Fichiers

Les systèmes d’exploitation organisent les fichiers qu’ils gèrent dans une arborescence de répertoires. Chaque répertoire contient un ensemble de fichiers identifés de manière unique (au sein du répertoire) par un nom. Il faut bien distinguer l’emplacement physique du fichier sur le disque et son emplacement logique dans l’arbre des répertoires du système. Ces deux aspects sont indépendants : il est possible de changer le nom d’un fichier ou de modifier son répertoire sans que cela affecte ni son emplacement physique ni son contenu.

Organisation de fichier

Du point de vue du SGBD, un fichier est une liste de blocs, regroupés sur certaines pistes ou répartis aléatoirement sur l’ensemble du disque et chaînés entre eux. La première solution est bien entendu préférable pour obtenir de bonnes performances, et les SGBD tentent dans la mesure du possible de gérer des fichiers constitués de blocs consécutifs. Quand il n’est pas possible de stocker un fichier sur un seul espace contigu (par exemple un seul cylindre du disque), une solution intermédiaire est de chaîner entre eux de tels espaces.

Le terme d’organisation pour un fichier désigne la structure utilisée pour stocker les enregistrements du fichier. Une bonne organisation a pour but de limiter les ressources en espace et en temps consacrées à la gestion du fichier.

  • Espace. La situation optimale est celle où la taille d’un fichier est la somme des tailles des enregistrements du fichier. Cela implique qu’il y ait peu, ou pas, d’espace inutilisé dans le fichier.
  • Temps. Une bonne organisation doit favoriser les opérations sur un fichier. En pratique, on s’intéresse plus particulièrement à la recherche d’un enregistrement, notamment parce que cette opération conditionne l’efficacité de la mise à jour et de la destruction. Il ne faut pas pour autant négliger le coût des insertions.

L’efficacité en espace peut être mesurée comme le rapport entre le nombre de blocs utilisés et le nombre minimal de blocs nécessaire. Si, par exemple, il est possible de stocker 4 enregistrements dans un bloc, un stockage optimal de 1000 enregistrements occupera 250 blocs. Dans une mauvaise organisation il n’y aura qu’un enregistrement par bloc et 1000 blocs seront nécessaires. Dans le pire des cas l’organisation autorise des blocs vides et la taille du fichier devient indépendante du nombre d’enregistrements.

Il est difficile de garantir une utilisation optimale de l’espace à tout moment à cause des destructions et modifications. Une bonne gestion de fichier doit avoir pour but – entre autres – de réorganiser dynamiquement le fichier afin de préserver une utilisation satisfaisante de l’espace.

L’efficacité en temps d’une organisation de fichier se définit en fonction d’une opération donnée (par exemple l’insertion, ou la recherche) et se mesure par le rapport entre le nombre de blocs lus et la taille totale du fichier. Pour une recherche par exemple, il faut dans le pire des cas lire tous les blocs du fichier pour trouver un enregistrement, ce qui donne une complexité linéaire. Certaines organisations permettent d’effectuer des recherches en temps sous-linéaire : arbres-B (temps logarithmique) et hachage (temps constant).

Une bonne organisation doit réaliser un bon compromis pour les quatres principaux types d’opérations :

  • insertion d’un enregistrement ;
  • recherche d’un enregistrement ;
  • mise à jour d’un enregistrement ;
  • destruction d’un enregistrement.

Dans ce qui suit nous discutons de ces quatre opérations sur la structure la plus simple qui soit, le fichier séquentiel (non ordonné). Le chapitre suivant est consacré aux techniques d’indexation et montrera comment on peut optimiser les opérations d’accès à un fichier séquentiel.

Dans un fichier séquentiel (sequential file ou heap file), les enregistrements sont stockés dans l’ordre d’insertion, et à la première place disponible. Il n’existe en particulier aucun ordre sur les enregistrements qui pourrait faciliter une recherche. En fait, dans cette organisation, on recherche plutôt une bonne utilisation de l’espace et de bonnes performances pour les opérations de mise à jour.

Recherche

La recherche consiste à trouver le ou les enregistrements satisfaisant un ou plusieurs critères. On peut rechercher par exemple tous les films parus en 2001, ou bien ceux qui sont parus en 2001 et dont le titre commence par ‘V’, ou encore n’importe quelle combinaison booléenne de tels critères.

La complexité des critères de sélection n’influe pas sur le coût de la recherche dans un fichier séquentiel. Dans tous les cas on doit partir du début du fichier, lire un par un tous les enregistrements en mémoire centrale, et effectuer à ce moment-là le test sur les critères de sélection. Ce test s’effectuant en mémoire centrale, sa complexité peut être considérée comme négligeable par rapport au temps de chargement de tous les blocs du fichier.

Quand on ne sait par à priori combien d’enregistrements on va trouver, il faut systématiquement parcourir tout le fichier. En revanche, si on fait une recherche par clé unique, on peut s’arrêter dès que l’enregistrement est trouvé. Le coût moyen est dans ce cas égal à \(\frac{n}{2}\), n étant le nombre de blocs.

Si le fichier est trié sur le champ servant de critère de recherche, il est possible d’effectuer un recherche par dichotomie qui est beaucoup plus efficace. Prenons l’exemple de la recherche du film Scream. L’algorithme est simple :

  • prendre le bloc au milieu du fichier ;
  • si on y trouve Scream la recherche est terminée ;
  • sinon, soit les films contenus dans le bloc précèdent Scream dans l’ordre lexicographique, et la recherche doit continuer dans la partie droite, du fichier, soit la recherche doit continuer dans la partie gauche ;
  • on recommence à l’étape (1), en prenant pour espace de rercherche la moitié droite ou gauche du fichier, selon le résultat de l’étape 2.

L’algorithme est récursif et permet de diminuer par deux, à chaque étape, la taille de l’espace de recherche. Si cette taille, initialement, est de n blocs, elle passe à \(\frac{n}{2}\) à l’étape 1, à \(\frac{n}{2^2}\) à l’étape 2, et plus généralement à \(\frac{n}{2^k}\) à l’étape k.

Au pire, la recherche se termine quand il n’y a plus qu’un seul bloc à explorer, autrement dit quand k est tel que \(n < 2^k\). On en déduit le nombre maximal d’étapes : c’est le plus petit k tel que \(n < 2^k\), soit \(log_2(n) < k\), soit \(k = \lceil log_2(n) \rceil\).

Pour un fichier de 100 Mo, un parcours séquentiel implique la lecture des 25~000 blocs, alors qu’une recherche par dichotomie ne demande que \(\lceil log_2(25000) \rceil=15\) lectures de blocs !! Le gain est considérable.

L’algorithme décrit ci-dessus se heurte cependant en pratique à deux obstacles.

  • en premier lieu il suppose que le fichier est organisé d’un seul tenant, et qu’il est possible à chaque étape de calculer le bloc du milieu ; en pratique cette hypothèse est très difficile à satisfaire ;
  • en second lieu, le maintien de l’ordre dans un fichier soumis à des insertions, suppressions et mises à jour est très difficile à obtenir.

Cette idée de se baser sur un tri pour effectuer des recherches efficaces est à la source de très nombreuses structures d’index qui seront étudiées dans le chapitre suivant. L’arbre-B, en particulier, peut être vu comme une structure résolvant les deux problèmes ci-dessus. D’une part il se base sur un système de pointeurs décrivant, à chaque étape de la recherche, l’emplacement de la partie du fichier qui reste à explorer, et d’autre part il utilise une algorithmique qui lui permet de se réorganiser dynamiquement sans perte de performance.

Mises à jour

Au moment où on doit insérer un nouvel enregistrement dans un fichier, le problème est de trouver un bloc avec un espace libre suffisant. Il est hors de question de parcourir tous les blocs, et on ne peut pas se permettre d’insérer toujours à la fin du fichier car il faut réutiliser les espaces rendus disponibles par les destructions. La seule solution est de garder une structure annexe qui distingue les blocs pleins des autres, et permette de trouver rapidement un bloc avec de l’espace disponible. Nous présentons deux structures possibles.

La première est une liste doublement chaînée des blocs libres (voir Fig. 3.6). Quand de l’espace se libère dans un bloc plein, on l’insère à la fin de la liste chaînée. Inversement, quand un bloc libre devient plein, on le supprime de la liste. Dans l’exemple de la Fig. 3.6, en imaginant que le bloc 8 devienne plein, on chainera ensemble les blocs 3 et 7 par un jeu classique de modification des adresses. Cette solution nécessite deux adresses (bloc précédent et bloc suivant) dans l’en-tête de chaque bloc, et l’adresse du premier bloc de la liste dans l’en-tête du fichier.

_images/pagelibres1.png

Fig. 3.6 Gestion des blocs libres avec liste chaînée

Un inconvénient de cette structure est qu’elle ne donne pas d’indication sur la quantité d’espace disponible dans les blocs. Quand on veut insérer un enregistrement de taille volumineuse, on risque d’avoir à parcourir une partie de la liste – et donc de lire plusieurs blocs – avant de trouver celui qui dispose d’un espace suffisant.

La seconde solution repose sur une structure séparée des blocs du fichier. Il s’agit d’un répertoire qui donne, pour chaque page, un indicateur O/N indiquant s’il reste de l’espace, et un champ donnant le nombre d’octets ( Fig. 3.7). Pour trouver un bloc avec une quantité d’espace libre donnée, il suffit de parcourir ce répertoire.

_images/pagelibres2.png

Fig. 3.7 Gestion des blocs libres avec répertoire

Le répertoire doit lui-même être stocké dans une ou plusieurs pages associées au fichier. Dans la mesure où l’on n’y stocke que très peu d’informations par bloc, sa taille sera toujours considérablement moins élévée que celle du fichier lui-même, et on peut considérer que le temps d’accès au répertoire est négligeable comparé aux autres opérations.

Quiz

  • Quelle est la différence entre un champ de type varchar(25) et un champ de type varchar(250)?

    • Le premier occupe 10 fois moins de place.
    • le système refusera de stocker dans le premier une chaîne de plus de 25 octets.
    • l’entête de l’enregistrement sera plus volumineuse pour le second:
  • Je représente l’adresse d’un enregistrement par son numéro de bloc B, et son numéro interne au bloc i (schéma d’indirection, vu ci-dessus). Qu’est-ce qui est vrai/faux?

    • Je peux aller lire directement l’enregistrement sur le disque.
    • Je dois d’abord lire le bloc avant de trouver l’enregistrement.
    • Je peux déplacer l’enregistrement librement dans le bloc sans changer son adresse.
    • L’enregistrement est toujours en position i dans le bloc.
  • Un programmeur paresseux dêcide de simplifier la méthode d’insertion en insérant tous les nouveaux enregistrements dans le premier bloc du fichier. Qu’en pensez-vous (en supposant que le système applique les méthodes de stockage vues précédemment)?

    • Ca va coincer dès que le premier bloc est plein.
    • Ca va marcher, il faudra un, ou au pire deux accès pour insérer un enregistrement.
    • Ca va marcher, mais le temps d’insertion devient imprévisible.
    • Ca va marcher, mais le temps d’insertion devient proportionnel à la taille du fichier.
  • Mêmes question, avec la méthode d’insertion standard consistant à insérer dans le dernier bloc du fichier.
  • Je veux que mon fichier soit trié sur la clé. Quelles affirmations sont vraies?

    • Chaque insertion nécessite le parcours de tout le fichier pour trouver le bon emplacement.
    • Quand un bloc est plein, je peux appliquer la méthode de chaînage vue en cours.
    • Si je connais l’adresse d’un enregistrement, je peux y accéder avec une lecture de bloc en moyenne.
    • Si je connais l’adresse d’un enregistrement, dans le cas le pire, il faudra lire tous les blocs..
  • Je veux stocker dans ma base, pour chaque film, son fichier vidéo MP4. Je choisis de créer un champ de type BIT VARYING pour le MP4. Qu’est-ce qui est vrai/faux.

    • Je ne peux pas car la taille d’un enregistrement sera supérieure à celle d’un bloc.
    • Cela va ralentir considérablement le temps de parcours séquentiel de ma table.
    • Je peux récupérer la vidéo grâce à une simple requête SQL.
    • En sauvegardant ma base je sauvegarde aussi les vidéos.
    • Cela va améliorer mon hit ratio.

Exercices

Exercice: quelques calculs

Soit la table de schéma suivant:

CREATE TABLE Personne (id     INT NOT NULL,
          nom    VARCHAR(40) NOT NULL,
          prenom VARCHAR(40) NOT NULL,
          adresse VARCHAR(70),
          dateNaissance DATE)

Cette table contient 300 000 enregistrements, stockés dans des blocs de taille 4 096 octets. Un enregistrement ne peut pas chevaucher deux blocs, et chaque bloc comprend un entête de 200 octets.

  • Donner la taille maximale et la taille minimale d’un enregistrement. On suppose par la suite que tous les enregistrements ont une taille maximale.
  • Quel est le nombre maximal d’enregistrements par bloc?
  • Quelle est la taille du fichier?
  • Quel est le temps moyen de recherche d’un enregistrement dans les deux cas suivants,: (a) les blocs sont stockés le plus contigument possible et (b) les blocs sont distribués au hasard sur le disque.
  • On suppose que le fichier est trié sur le nom. Quel est le temps d’une recherche dichotomique pour chercher une personne avec un nom donné?

S2: Oracle

Supports complémentaires:

Le système de représentation physique d’Oracle est assez riche et repose sur une terminologie qui porte facilement à confusion. En particulier les termes de “représentation physique” et “représentation logique” ne sont pas employés dans le sens que nous avons adopté jusqu’à présent. Pour des raisons de clarté, nous adaptons, quand c’est nécessaire, la terminologie d’Oracle pour rester cohérent avec celle que nous avons employée précédemment.

Avertissement

Cette section décrit brièvement les outils de gestion physique d’Oracle pour montrer une mise en pratique concrète des principes généraux détaillés précédemment. Elle ne prétend pas être une référence sur le sujet. Il se peut par ailleurs que les évolutions d’Oracle depuis le rédaction de cette section aient rendu certains détails obsolètes.

Un système Oracle (une instance dans la documentation) stocke les données dans un ou plusieurs fichiers. Ces fichiers sont entièrement attribués au SGBD, qui est seul à organiser leur contenu. Ils sont divisés en blocs dont la taille – paramétrable – peut varier de 1K à 8K. Au sein d’un fichier des blocs consécutifs peuvent être regroupés pour former des extensions (“extent”). Enfin un ensemble d’extensions permettant de stocker un des objets physiques de la base (une table, un index) constitue un segment.

Il est possible de paramétrer, pour un ou plusieurs fichiers, le mode de stockage des données. Ce paramétrage comprend, entre autres, la taille des extensions, le nombre maximal d’extensions formant un segment, le pourcentage d’espace libre laissé dans les blocs, etc. Ces paramètres, et les fichiers auxquels ils s’appliquent, portent le nom de tablespace.

Nous revenons maintenant en détail sur ces concepts.

Fichiers et blocs

Au moment de la création d’une base de données, il faut attribuer à Oracle au moins un fichier sur un disque. Ce fichier constitue l’espace de stockage initial qui contiendra, au départ, le dictionnaire de données.

La taille de ce fichier est choisie par l’administrateur de bases de données (DBA), et dépend de l’organisation physique qui a été choisie. On peut allouer un seul gros fichier et y placer toutes les données et tous les index, ou bien restreindre ce fichier initial au stockage du dictionnaire et ajouter d’autres fichiers, un pour les index, un pour les données, etc. Le deuxième type de solution est sans doute préférable, bien qu’un peu plus complexe. Il permet notamment, en plaçant les fichiers sur plusieurs disques, de bien répartir la charge des contrôleurs de disque. Une pratique courante – et recommandée par Oracle – est de placer un fichier de données sur un disque et un fichier d’index sur un autre. La répartition sur plusieurs disques permet en outre, grâce au paramétrage des tablespaces qui sera étudié plus loin, de régler finement l’utilisation de l’espace en fonction de la nature des informations – données ou index – qui y sont stockées.

Les blocs ORACLE

Le bloc est la plus petite unité de stockage gérée par ORACLE. La taille d’un bloc peut être choisie au moment de l’initialisation d’une base, et correspond obligatoirement à un multiple de la taille des blocs du système d’exploitation. À titre d’exemple, un bloc dans un système comme Linux occupe 1024 octets, et un bloc ORACLE occupe typiquement 4 096 ou 8 092 octets.

_images/bloc-oracle.png

Fig. 3.8 Structure d’un bloc Oracle

La structure d’un bloc est identique quel que soit le type d’information qui y est stocké. Elle est constituée des cinq parties suivantes (Fig. 3.8):

  • l’entête (header) contient l’adresse du bloc, et son type (données, index, etc);
  • le répertoire des tables donne la liste des tables pour lesquelles des informations sont stockées dans le bloc;
  • le répertoire des enregistrements contient les adresses des enregistrements du bloc;
  • un espace libre est laissé pour faciliter l’insertion de nouveaux enregistrements, ou l’agrandissement des enregistrements du bloc (par exemple un attribut à NULL auquel on donne une valeur par un update).
  • enfin, l’espace des données contient les enregistrements.

Les trois premières parties constituent un espace de stockage qui n’est pas directement dédié aux données (Oracle le nomme l’overhead). Cet espace “perdu” occupe environ 100 octets. Le reste permet de stocker les données des enregistrements.

Les paramètres pctfree et pctused

La quantité d’espace libre laissée dans un bloc peut être spécifiée grâce au paramètre pctfree, au moment de la création d’une table ou d’un index. Par exemple une valeur de 30% indique que les insertions se feront dans le bloc jusqu’à ce que 70% du bloc soit occupé, les 30% restant étant réservés aux éventuels agrandissements des enregistrements. Une fois que cet espace disponible de 70% est rempli, Oracle considère qu’aucune nouvelle insertion ne peut se faire dans ce bloc.

Notez qu’il peut arriver, pour reprendre l’exemple précédent, que des modifications sur les enregistrements (mise à NULL de certains attributs par exemple) fassent baisser le taux d’occupation du bloc. Quand ce taux baisse en dessous d’une valeur donnée par le paramètre pctused , Oracle considère que le bloc est à nouveau disponible pour des insertions.

En résumé, pctfree indique le taux d’utilisation maximal au-delà duquel les insertions deviennent interdites, et pctused indique le taux d’utilisation minimal en-deçà duquel ces insertions sont à nouveau possibles. Les valeurs de ces paramètres dépendent des caractéristiques des données stockées dans une table particulière. Une petite valeur pour pctfree permet aux insertions de remplir plus complètement le bloc, et peut donc mieux exploiter l’espace disque. Ce choix peut être valable pour des données qui sont rarement modifiées. En contrepartie une valeur plus importante de pctfree va occuper plus de blocs pour les mêmes données, mais offre plus de flexibilité pour des mises à jour fréquentes.

Voici deux scénarios possibles pour spécifier pctused et pctfree. Dans le premier, pctfree vaut 30%, et pctused 40% (notez que la somme de ces deux valeurs ne peut jamais excéder 100%). Les insertions dans un bloc peuvent donc s’effectuer jusqu’à ce que 70% du bloc soit occupé. Le bloc est alors retiré de la liste des blocs disponibles pour des insertions, et seules des mises à jour (destructions ou modifications) peuvent affecter son contenu. Si, à la suite de ces mises à jour, l’espace occupé tombe en-dessous de 40%, le bloc est à nouveau marqué comme étant disponible pour des insertions.

Dans ce premier scénario, on accepte d’avoir beaucoup d’expace innoccupé, au pire 60%. L’avantage est que le coût de maintenance de la liste des blocs disponibles pour l’insertion est limité pour Oracle.

Dans le second scénario, pctfree vaut 10% (ce qui est d’ailleurs la valeur par défaut), et pctused 80%. Quand le bloc est plein à 90%, les insertions s’arrêtent, mais elles reprennent dès que le taux d’occupation tombe sous 80%. On est assuré d’une bonne utilisation de l’espace, mais le travail du SGBD est plus important (et donc pénalisé) puisque la gestion des blocs disponibles/indisponibles devient plus intensive. De plus, en ne laissant que 10% de marge de manœuvre pour d’éventuelles extensions des enregistrements, on s’expose éventuellement à la nécessité de chaîner les enregistrements sur plusieurs blocs.

Enregistrements

Un enregistrement est une suite de données stockés, à quelques variantes près, comme décrit dans la session précédente. Par exemple les données de type CHAR(n) sont stockées dans un tableau d’octets de longueur n+1. Le premier octet indique la taille de la chaîne, qui doit donc être comprise entre 1 et 255. Les n octets suivants stockent les caractères de la chaînes, complétés par des blancs si la longueur de cette dernière est inférieure à la taille maximale. Pour les données de type VARCHAR(n) en revanche, seuls les octets utiles pour la représentation de la chaîne sont stockés. C’est un cas où une mise à jour élargissant la chaîne entraîne une réorganisation du bloc.

Chaque attribut est précédé de la longueur de stockage. Dans Oracle les valeurs NULL sont simplement représentées par une longueur de 0. Cependant, si les n derniers attributs d’un enregistrement sont NULL, ORACLE se contente de placer une marque de fin d’enregistrement, ce qui permet d’économiser de l’espace.

Chaque enregistrement est identifié par un ROWID, comprenant trois parties:

  • le numéro du bloc au sein du fichier;
  • le numéro de l’enregistrement au sein du bloc;
  • enfin l’identifiant du fichier.

Un enregistrement peut occuper plus d’un bloc, notamment s’il contient les attributs de type LONG. Dans ce cas ORACLE utilise un chaînage vers un autre bloc. Un situation comparable est celle de l’agrandissement d’un enregistrement qui va au-delà de l’espace libre disponible. Dans ce cas ORACLE effctue une migration: l’enregistrement est déplacé en totalité dans un autre bloc, et un pointeur est laissé dans le bloc d’origine pour ne pas avoir à modifier l’adresse de l’enregistrement (ROWID). Cette adresse peut en effet être utilisée par des index, et une réorganisation totale serait trop coûteuse. Migration et chaînage sont bien entendu pénalisants pour les performances.

Extensions et segments

Une extension est un suite contiguë (au sens de l’emplacement sur le disque) de blocs. En général une extension est affectée à un seul type de données (par exemple les enregistrements d’une table). Comme nous l’avons vu en détail, cette contiguïté est un facteur essentiel pour l’efficacité de l’accès aux données, puisqu’elle évite les déplacements des têtes de lecture, ainsi que le délai de rotation.

Le nombre de blocs dans une extension peut être spécifié par l’administrateur. Bien entendu des extensions de tailles importante favorisent de bonnes performances, mais il existe des contreparties:

  • si une table ne contient que quelques enregistrements, il est inutile de lui allouer une extension contenant des milliers de blocs;
  • l’utilisation et la réorganisation de l’espace de stockage peuvent être plus difficiles pour des extensions de grande taille.

Les extensions sont l’unité de stockage constituant les segments. Si on a par exemple indiqué que la taille des extensions est de 50 blocs, un segment (de données ou d’index) consistera en n extensions de 50 blocs chacune. Il existe quatre types de segments:

  • les segments de données contiennent les enregistrements des tables, avec un segment de ce type par table;
  • les segments d’index contiennent les enregistrements des index; il y a un segment par index;
  • les segments temporaires sont utilisés pour stocker des données pendant l’exécution des requêtes (par exemple pour les tris);
  • enfin les segments rollbacks contiennent les informations permettant d’effectuer une reprise sur panne ou l’annulation d’une transaction; il s’agit typiquement des données avant modification, dans une transaction qui n’a pas encore été validée.

Une extension initiale est allouée à la création d’un segment. De nouvelles extensions sont allouées dynamiquement (autrement dit, sans intervention de l’administrateur) au segment au fur et à mesure des insertions: rien ne peut garantir qu’une nouvelle extension est contiguë avec les précédentes. En revanche une fois qu’une extension est affectée à un segment, il faut une commande explicite de l’administrateur, ou une destruction de la table ou de l’index, pour que cette extension redevienne libre.

Quand Oracle doit créer une nouvelle extension et se trouve dans l’incapacité de constituer un espace libre suffisant, une erreur survient. C’est alors à l’administrateur d’affecter un nouveau fichier à la base, ou de réorganiser l’espace dans les fichiers existant.

Les tablespaces

Un tablespace est un espace physique constitué de un (au moins) ou plusieurs fichiers. Une base de données Oracle est donc organisée sous la forme d’un ensemble de tablespace, sachant qu’il en existe toujours un, créé au moment de l’initialisation de la base, et nommé SYSTEM. Ce tablespace contient le dictionnaire de données, y compris les procédures stockées, les triggers, etc.

L’organisation du stockage au sein d’un tablespace est décrite par de nombreux paramètres (taille des extensions, nombre maximal d’extensions, etc.) qui sont donnés à la création du tablespace, et peuvent être modifiés par la suite. C’est donc au niveau du tablespace (et pas au niveau du fichier) que l’administrateur de la base peut décrire le mode de stockage des données. La création de plusieurs tablespaces, avec des paramètres de stockage individualisés, offre de nombreuses possibilités:

  • adaptation du mode de stockage en fonction d’un type de données particulier;
  • affectation d’un espace disque limité aux utilisateurs;
  • contrôle sur la disponibilité de parties de la base, par mise hors service d’un ou plusieurs tablespaces;
  • enfin – et surtout – répartition des données sur plusieurs disques afin d’améliorer les performances.

Un exemple typique est la séparation des données et des index, si possible sur des disques différents, afin d’optimiser la charge des contrôleurs de disque. Il est également possible de créer des tablespaces dédiées aux données temporaires ce qui évite de mélanger les enregistrements des tables et ceux temporairement créés au cours d’une opération de tri. Enfin un tablespace peut être placé en mode de lecture, les écritures étant interdites. Toutes ces possibilités donnent beaucoup de flexibilité pour la gestion des données, aussi bien dans un but d’améliorer les performances que pour la sécurité des accès.

Au moment de la création d’un tablespace, on indique les paramètres de stockage par défaut des tables ou index qui seront stockés dans ce tablespace. L’expression “par défaut” signifie qu’il est possible, lors de la création d’une table particulière, de donner des paramètres spécifiques à cette table, mais que les paramètres du tablespace s’appliquent si on ne le fait pas. Les principaux paramètres de stockage sont:

  • la taille de l’extension initiale (par défaut 5 blocs);
  • la taille de chaque nouvelle extension (par défaut 5 blocs également);
  • le nombre maximal d’extensions, ce qui donne donc, avec la taille des extensions, le nombre maximal de blocs alloués à une table ou index;
  • la taille des extensions peut croître progressivement, selon un ratio indiqué par pctincrease; une valeur de 50% pour ce paramètre indique par exemple que chaque nouvelle extension a une taille supérieure de 50% à la précédente.

Voici un exemple de création de tablespace.

CREATE TABLESPACE TB1
  DATAFILE 'fichierTB1.dat' SIZE 50M
  DEFAULT STORAGE (
    INITIAL 100K
    NEXT 40K
    MAXEXTENTS 20,
     PCTINCREASE 20);

La commande crée un tablespace, nommé TB1, et lui affecte un premier fichier de 50 mégaoctets. Les paramètres de la partie DEFAULT STORAGE indiquent, dans l’ordre:

  • la taille de la première extension allouée à une table (ou un index);
  • la taille de la prochaine extension, si l’espace alloué à la table doit être agrandi;
  • le nombre maximal d’extensions, ici 20;
  • enfin chaque nouvelle extension est 20% plus grande que la précédente.

En supposant que la taille d’un bloc est 4K, on obtient une première extension de 25 blocs, une seconde de 10 blocs, une troisième de \(10 \times 1,2 = 12\) blocs, etc.

Le fait d’indiquer une taille maximale permet de contrôler que l’espace ne sera pas utilisé sans limite, et sans contrôle de l’administrateur. En contrepartie, ce dernier doit être prêt à prendre des mesures pour répondre aux demandes des utilisateurs quand des messages sont produits par Oracle indiquant qu’une table a atteint sa taille limite.

Voici un exemple de tablespace défini avec un paramétrage plus souple: d’une part il n’y a pas de limite au nombre d’extensions d’une table, d’autre part le fichier est en mode auto-extension, ce qui signifie qu’il s’étend automatiquement, par tranches de 5 mégaoctets, au fur et à mesure que les besoins en espace augmentent. La taille du fichier est elle-même limitée à 500 mégaoctets.

CREATE TABLESPACE TB2
  DATAFILE 'fichierTB2.dat' SIZE 2M
  AUTOEXTEND ON NEXT 5M MAXSIZE 500M
  DEFAULT STORAGE (INITIAL 128K NEXT 128K MAXEXTENTS UNLIMITED);

Il est possible, après la création d’un tablespace, de modifier ses paramètres, étant entendu que la modification ne s’applique pas aux tables existantes mais à celles qui vont être créées. Par exemple on peut modifier le tablespace TB1 pour que les extensions soient de 100K, et le nombre maximal d’extensions porté à 200.

ALTER TABLESPACE TB1
   DEFAULT STORAGE (
    NEXT 100K
     MAXEXTENTS 200);

Voici quelque-unes des différentes actions disponibles sur un tablespace,:

  • On peut mettre un tablespace hors-service, soit pour effectuer une sauvegarde d’une partie de la base, soit pour rendre cette partie de la base indisponible.

    ALTER TABLESPACE TB1 OFFLINE;
    

    Cette commande permet en quelque sorte de traiter un tablespace comme une sous-base de données.

  • On peut mettre un tablespace en lecture seule.

    ALTER TABLESPACE TB1 READ ONLY;
    
  • Enfin on peut ajouter un nouveau fichier à un tablespace afin d’augmenter sa capacité de stockage.
ALTER TABLESPACE ADD DATAFILE 'fichierTB1-2.dat' SIZE 300 M;

Au moment de la création d’une base, on doit donner la taille et l’emplacement d’un premier fichier qui sera affecté au tablespace SYSTEM. À chaque création d’un nouveau tablespace par la suite, il faudra créer un fichier qui servira d’espace de stockage initial pour les données qui doivent y être stockées. Il faut bien noter qu’un fichier n’appartient qu’à un seul tablespace, et que, dès le moment où ce fichier est créé, son contenu est exlusivement géré par Oracle, même si une partie seulement est utilisée. En d’autres termes il ne faut pas affecter un fichier de 1 Go à un tablespace destiné seulement à contenir 100 Mo de données, car les 900 Mo restant ne servent alors à rien.

Oracle utilise l’espace disponible dans un fichier pour y créer de nouvelles extensions quand la taille des données augmente, ou de nouveaux segments quand des tables ou index sont créés. Quand un fichier est plein – ou, pour dire les choses plus précisément, quand Oracle ne trouve pas assez d’espace disponible pour créer un nouveau segment ou une nouvelle extension –, un message d’erreur avertit l’administrateur qui dispose alors de plusieurs solutions,:

  • créer un nouveau fichier, et l’affecter au tablespace (voir la commande ci-dessus);
  • modifier la taille d’un fichier existant;
  • enfin, permettre à un ou plusieurs fichiers de croître dynamiquement en fonction des besoins, ce qui peut simplifier la gestion de l’espace.

Comment inspecter les tablespaces

Oracle fournit un certain nombre de vues dans son dictionnaire de données pour consulter l’organisation physique d’une base, et l’utilisation de l’espace.

  • La vue DBA_EXTENTS donne la liste des extensions;
  • La vue DBA_SEGMENTS donne la liste des segments;
  • La vue DBA_FREE_SPACE permet de mesurer l’espace libre;
  • La vue DBA_TABLESPACES donne la liste des tablespaces;
  • La vue DBA_DATA_FILES donne la liste des fichiers.

Ces vues sont gérées sous le compte utilisateur SYS qui est réservé à l’administrateur de la base. Voici quelques exemples de requêtes permettant d’inspecter une base. On suppose que la base contient deux tablespace, SYSTEM avec un fichier de 50M, et TB1 avec deux fichiers dont les tailles repectives sont 100M et 200M.

La première requête affiche les principales informations sur les tablespaces.

SELECT tablespace_name "TABLESPACE",
       initial_extent "INITIAL_EXT",
       next_extent "NEXT_EXT",
       max_extents "MAX_EXT"
FROM sys.dba_tablespaces;

On obtient quelque chose qui ressemble à:

TABLESPACE  INITIAL_EXT  NEXT_EXT     MAX_EXT
----------  -----------  --------     -------
SYSTEM      10240000     10240000          99
TB1         102400         50000          200

On peut obtenir la liste des fichiers d’une base, avec le tablespace auquel ils sont affectés:

Avec un résultat:

FILE_NAME    BYTES      TABLESPACE_NAME
------------ ---------- -------------------
fichier1      5120000   SYSTEM
fichier2     10240000   TB1
fichier3     20480000   TB1

Enfin on peut obtenir l’espace disponible dans chaque tablespace. Voici par exemple la requête qui donne des informations statistiques sur les espaces libres du tablespace SYSTEM.

SELECT tablespace_name, file_id,
   COUNT(*)    "PIECES",
   MAX(blocks) "MAXIMUM",
   MIN(blocks) "MINIMUM",
   AVG(blocks) "AVERAGE",
   SUM(blocks) "TOTAL"
FROM sys.dba_free_space
WHERE tablespace_name = 'SYSTEM'
GROUP BY tablespace_name, file_id;

Résultat:

TABLESPACE    FILE_ID  PIECES   MAXIMUM    MINIMUM  AVERAGE     SUM
----------    -------  ------   -------    -------  -------   ------
SYSTEM             1       2      2928        115   1521.5    3043

SUM donne le nombre total de blocs libres, PIECES montre la fragmentation de l’espace libre, et MAXIMUM donne l’espace contigu maximal. Ces informations sont utiles pour savoir s’il est possible de créer des tables volumineuses pour lesquelles on souhaite réserver dès le départ une extension de taille suffisante.

Création des tables

Tout utilisateur Oracle ayant les droits suffisants peut créer des tables. Notons que sous Oracle la notion d’utilisateur et celle de base de données sont liées,: un utilisateur (avec des droits appropriés) dispose d’un espace permettant de stocker des tables, et tout ordre CREATE TABLE effectué par cet utilisateur crée une table et des index qui appartiennent à cet utilisateur.

Il est possible, au moment où on spécifie le profil d’un utilisateur, d’indiquer dans quels tablespaces il a le droit de placer des tables, de quel espace total il dispose sur chacun de ces tablespaces, et quel est le tablespace par défaut pour cet utilisateur.

Il devient alors possible d’inclure dans la commande CREATE TABLE des paramètres de stockage. Voici un exemple,:

CREATE TABLE Film (...)
  PCTFREE 10
  PCTUSED 40
  TABLESPACE TB1
  STORAGE ( INITIAL 50K
                NEXT 50K
                MAXEXTENTS 10
                PCTINCREASE 25 );

On indique donc que la table doit être stockée dans le tablespace TB1, et on remplace les paramètres de stockage de ce tablespace par des paramètres spécifiques à la table Film.

Par défaut une table est organisée séquentiellement sur une ou plusieurs extensions. Les index sur la table sont stockés dans un autre segment, et font référence aux enregistrements grâce au ROWID.