Structure physique de la base de données (InterBase et Firebird)
Alexey Kovyazin, Sergey Vostrikov, dernière mise à jour le 05-juin-2004
Structure physique de la base de données
Pourquoi devons-nous étudier la structure physique d’une base de données InterBase ?
Habituellement, lorsque nous parlons de la structure physique d’une base de données InterBase, nous entendons par là qu’elle représente les données du point de vue de l’organisation des données à bas niveau - jusqu’au niveau des octets. De nombreux programmeurs développant des applications en langage de haut niveau négligent l’étude des détails de bas niveau. Cependant, connaître les principes principaux de l’organisation des données à l’intérieur de la base de données est une clé pour la conception efficace d’applications de bases de données. C’est pourquoi nous ferons une excursion dans les entrailles de l’organisation d’une base de données InterBase et nous découvrirons comment elle est agencée.
Alors, à quoi sert un système de gestion de base de données (SGBD) ? Évidemment à stocker et à contrôler les données. Cela semble banal, mais cela vaut la peine d’y réfléchir. Un utilisateur confie les données au SGBD, qui traduit d’une certaine manière ces données dans des formats internes qui lui sont compréhensibles. Vous pouvez imaginer des « 0 et 1 » si les mots « format de données interne » posent quelques difficultés d’association. Le SGBD stocke ces données et, au moment de la première demande, doit les extraire de son format, les convertir dans une vue appropriée et les donner à un utilisateur.
Le sujet de ce chapitre est de savoir comment le SGBD stocke ses données, sous quelle vue et comment elles sont organisées au niveau le plus bas. Nous allons essayer de vous expliquer comment, à partir de bits et d’octets se trouvant sur le disque dur, nous obtenons des données précieuses.
Fichiers de base de données InterBase
Habituellement, lorsque nous parlons d’une base de données, nous entendons le SGBD lui-même et les informations utilisateur, et même les programmes clients qui travaillent avec les données. Dans ce chapitre, nous considérerons une base de données comme des fichiers de base de données.
Une base de données InterBase représente un ou plusieurs fichiers contenant des informations sur tout ce qui est lié à cette base. Les informations sur les utilisateurs sont une exception car les utilisateurs sont définis au niveau du serveur entier et sont stockés séparément, dans la base de données de sécurité admin.ib (c’était ISC4.GDB dans les versions antérieures à 7).
Conseil : Consultez le chapitre « Sécurité du serveur et de la base de données » pour en savoir plus sur les principes de sécurité d’InterBase.
Ainsi, toutes les informations sur la base de données sont stockées dans ces fichiers : les données elles-mêmes, les index, les déclencheurs, les procédures stockées, etc.
Une base de données InterBase pour un projet moyen représente un seul fichier car les versions modernes d’InterBase peuvent utiliser l’E/S 64 bits pour travailler avec le fichier de données, ce qui vous donne la possibilité d’avoir un fichier de données jusqu’à 64 Go. Les versions antérieures d’InterBase avaient la contrainte de 4 gigaoctets par fichier de base de données (jusqu’à 64 To pour l’ensemble de la base de données). Comme nous pouvons le supposer, 64 gigaoctets sont largement suffisants pour stocker les informations de presque toute application de base de données. Mais si nécessaire, nous pouvons diviser une base de données en plusieurs fichiers. D’ailleurs, il existe des bases de données InterBase de plusieurs centaines de gigaoctets.
IBSurgeon - un guide à travers la base de données InterBase
Nous devons connaître en détail la structure des fichiers de base de données InterBase. Et il est donc souhaitable de disposer d’un outil pratique qui permette de travailler directement avec les fichiers de base de données, non pas au moyen du noyau du serveur InterBase. Le moyen le plus simple est d’utiliser un visualiseur hexadécimal ordinaire et d’essayer de comprendre la structure des fichiers de base de données en considérant leur représentation HEX. Ce serait un travail plutôt fastidieux.
Mais heureusement, il existe un outil pour un travail direct avec les bases de données InterBase. C’est l’IBSurgeon Editor - un outil pour un travail direct de bas niveau avec les bases de données InterBase, qui peut être utilisé pour étudier la structure interne des bases de données InterBase et pour diagnostiquer les bases de données corrompues afin de les restaurer. Pour plus de détails, voir l’annexe « Outils pour administrateur et développeur InterBase ».
IBSurgeon utilise son propre mécanisme alternatif d’accès aux bases de données qui permet d’ouvrir et d’examiner les bases de données dans n’importe quel état, y compris celles gravement corrompues qui ne peuvent pas être ouvertes par le noyau du serveur InterBase/FireBird/Yaffil.
Nous utiliserons IBSurgeon pour illustrer la structure interne de la base de données.
Fichiers *.IB/*.FDB de l’intérieur
IB est une extension recommandée pour les fichiers de base de données InterBase, et FDB pour Firebird (auparavant c’était GDB). La première chose que nous devons dire à propos de la structure du fichier IB est qu’il représente un ensemble de pages de taille strictement définie. La taille du fichier de base de données est divisible par une taille de page, qui est inchangée pour tous les fichiers de cette base de données. Différentes versions d’InterBase supportent différentes tailles de page, comme le montre le tableau 1. La taille de page est définie lors de la création d’une base de données et ne peut pas être modifiée pendant son cycle de vie. En d’autres termes, nous ne pouvons changer la taille de page que lors de la restauration d’une base de données à partir d’une sauvegarde.
Tableau 1. Tailles de page supportées par différentes versions d’InterBase
| Version InterBase | Taille de page, octets | ||||
| 1024 | 2048 | 4096 | 8192 | 16384 | |
| InterBase 4.0 | * | * | * | * | |
| InterBase 5.x | * | * | * | * | |
| InterBase 6.x-7.x | * | * | * | * |
La lecture et l’écriture des données dans une base de données sont exécutées page par page ; de nombreuses caractéristiques importantes du serveur et de la base de données, telles que la taille du cache de base de données, dépendent de la taille de page et sont comptées en « pages ».
Ouvrons n’importe quelle base de données InterBase avec IBSurgeon. Il suffit de double-cliquer sur le fichier de base de données. L’image 1 exprime une liste de pages qui apparaît après qu’IBSurgeon a ouvert la base de données :

Image 1. Une liste de pages de base de données
Les pages peuvent être de différents types, chacun servant à un objectif précis. Les interdépendances des différents types sont représentées conditionnellement sur l’image 2. L’image 2 représente schématiquement une allocation des pages dans le fichier de base de données - de gauche à droite, de haut en bas, si l’on compte depuis le début du fichier. Les pages du même type ne se succèdent pas strictement une par une - elles peuvent facilement être mélangées, allouées dans un fichier dans l’ordre où elles ont été créées par le serveur lors de l’extension ou de la création de bases de données.

Image 2. Interdépendances entre différents types de pages dans une base de données InterBase
Vous avez dû remarquer que certains types de pages n’ont pas de références à d’autres types de pages. Cependant, il n’y a pas de contradiction ici ; le fait est que ces types de pages sont liés et utilisés à un autre niveau structurel. Ils peuvent être liés à la table RDB$PAGES et à d’autres tables système (cette table et d’autres objets système seront examinés ci-dessous - dans le chapitre « Structure logique de la base de données »). Sur l’image 2, nous ne pouvons voir que les références explicites entre les pages au niveau physique.
Examinons en détail quels types de pages existent dans une base de données InterBase. Dans le fichier ods.h de l’ensemble des codes primaires d’InterBase, il y a des informations sur tous les types de pages possibles. Nous nous référerons fréquemment à ce fichier pour recevoir les données non seulement sur l’ODS mais aussi sur beaucoup d’autres choses fondamentales du noyau InterBase à la source originale. 11 types de pages sont déclarés au total, mais seulement 9 d’entre eux méritent d’être expliqués (nous le voyons clairement dans le tableau 2). Les types de pages avec les identifiants 0 et 1 sont indéfinis ou non utilisés.
Tableau 3. Types de pages dans FB
| La définition dans ods.h | Identifiant du type de page | Descriptions des pages |
| pag_undefined | 0 | Indéfini - Si une page a ce type de page, elle est probablement libre |
| pag_header | 1 | Page d’en-tête de base de données |
| pag_pages | 2 | Page d’inventaire des pages (ou page d’inventaire d’espace - SIP) |
| pag_transactions | 3 | Page d’inventaire des transactions (TIP) |
| pag_pointer | 4 | Page pointeur |
| pag_data | 5 | Page de données |
| pag_root | 6 | Page racine d’index |
| pag_index | 7 | Page d’index (arbre B) |
| pag_blob | 8 | Page de données Blob |
| pag_ids | 9 | Gen-ids |
| pag_log | 10 | Informations de journal d’écriture anticipée |
Chaque page a son en-tête, contenant des informations sur le type de page et un numéro de la page suivante du même type. Nous pouvons obtenir la liste complète des paramètres que chaque en-tête de page contient, si nous considérons la structure pag dans le fichier de définitions ods.h.
/\* En-tête de page de base */
typedef struct pag {
SCHAR pag_type; /*identifiant du type de page*/
SCHAR pag_flags; /*indicateurs de page*/
USHORT pag_checksum; /*somme de contrôle de page : elle est égale à 12345 après la version 5.0 */
ULONG pag_generation; /*génération de page */
ULONG pag_seqno; /* numéro de séquence WAL de la dernière mise à jour - obsolète*/
ULONG pag_offset; /* décalage WAL de la dernière mise à jour - obsolète*/
} *PAG;
Types de pages et leur utilisation
Examinons chaque type de page en détail et découvrons leur fonction et les informations qu’ils contiennent. Nous commencerons étape par étape - dès la première page.
Toute opération avec une base de données commence par la lecture de la page d’en-tête de base de données (ou page d’en-tête). La page d’en-tête de base de données vient en premier dans tous les fichiers de bases de données. En conséquence, elle est représentée en premier sur l’image 2 (si nous imaginons que l’image représente une extension du fichier de base de données de gauche à droite, de haut en bas).
Une page d’en-tête contient des informations sur la base de données dans son ensemble. Sur l’image 3, une page de données est exprimée de la manière dont IBSurgeon nous la montre :

Image 3. Page d’en-tête de base de données.
Vous pouvez avoir une idée du contenu de la page d’en-tête en obtenant les statistiques de la base de données. Pour cela, vous pouvez utiliser l’utilitaire de ligne de commande gstat ou un autre outil plus pratique pour l’administration d’InterBase de la liste dans l’application « Outils pour administrateur et développeur InterBase ». Pour plus de détails sur le processus d’obtention des statistiques et la description de l’en-tête, voir le chapitre « Statistiques ».
Il convient de noter que la page d’en-tête contient des informations importantes telles que la taille de page, le numéro de version ODS (des informations à ce sujet se trouvent ci-dessous), les données de création de la base de données, des informations sur les transactions et un ensemble d’informations diverses. Par exemple, l’ID d’implémentation stocke des informations sur le système d’exploitation sous lequel cette base de données a été créée.
Lors de la connexion à une base de données, le serveur InterBase lit les premiers 1024 octets d’informations depuis le début du fichier et définit selon les valeurs lues si le fichier indiqué dans la ligne de connexion est une base de données InterBase ou non. Ensuite, le serveur lit le numéro de version ODS depuis la page d’en-tête et la taille de page de cette base de données et, si la version ODS est compatible avec l’implémentation du serveur, il relit toute la page d’en-tête en utilisant la taille de page appropriée, reçue des premiers 1024 octets. Après cela, les autres paramètres importants de la base de données tels que le mode lecture-écriture, le dialecte de la base de données, etc. sont lus depuis la page d’en-tête.
Sur la page d’en-tête, il y a une référence à la première des pages pointeurs, stockant des références aux pages de données qui contiennent les métadonnées : la table RDB$Pages (voir ci-dessous dans le chapitre « Structure logique de la base de données InterBase »). Sur l’image 2, cette référence est illustrée par une flèche avec l’inscription « Numéro de la 1 ère page pointeur dans la base de données ». Le serveur lit le numéro de la 1 ère page pointeur depuis la page d’en-tête et s’y rend. La page pointeur consiste en un tableau ordonné de numéros de pages de données qui composent une certaine table (une table est considérée comme un objet SQL, décrit par la structure logique de la base de données). Vous pouvez maintenant voir comment IBSurgeon interprète la page pointeur (regardez l’image 4) :

Image 4. Page pointeur de base de données InterBase
La page contient un vecteur de pages de données ; ces données composent une certaine table dans une base de données. Ce vecteur représente un tableau de pointeurs correspondant aux numéros des pages de données dans le fichier. Le serveur lit un numéro de page de données sur 4 octets et se rend à la page de données nécessaire. Lorsqu’il se rend à la 1 ère page de données de RDB$Pages, le serveur commence à construire la représentation interne de la base de données qui sera utilisée ensuite par le serveur pour toutes les opérations avec la base de données. RDB$Pages stocke des références non seulement aux pages de données contenant des informations sur la base de données mais aussi aux autres pages qui jouent un rôle dans le fonctionnement de la base de données.
Nous mentionnons souvent cette table qui, à proprement parler, relève de la structure logique de la base de données. Néanmoins, tout est interconnecté, c’est pourquoi nous ne pouvons pas décrire quelque chose sans nous référer à autre chose.
L’un des types de pages importants est la page d’inventaire des transactions (TIP). Ces pages, comme toutes les pages, se composent d’un en-tête et d’une partie principale représentant un tableau de séquences de 2 octets. Les séquences décrivent l’état des transactions dans une base de données (pour plus de détails sur les transactions, voir le chapitre « Transactions »).
Tableau 4. États possibles des transactions dans TIP
| Valeur de la séquence sur PIP | Sens |
| 0 | La transition n’a pas commencé, est active ou perdue sans commit ni rollback |
| 1 | La transition a exécuté Commit |
| 2 | La transition a exécuté Rollback |
| 3 | Transition-limbo (pour 2PC) |
Chaque version d’enregistrement possède son identifiant de transaction, ce qui permet aux transactions exécutées simultanément d’« apprendre » l’état des unes et des autres et de résoudre les conflits lors d’un travail multi-utilisateur (voir le chapitre « Architecture multi-génération d’InterBase » pour en savoir plus sur les versions d’enregistrements et autres éléments).
La page d’en-tête de la base de données, les pages pointeurs et la TIP font référence aux types de pages « d’entretien », utilisés uniquement par le serveur. Les utilisateurs d’InterBase n’obtiennent jamais explicitement les informations qu’elles contiennent. Les pages qui stockent des informations sur l’allocation des pages (généralement appelées pages d’inventaire de pages (PIP) ou pages d’inventaire d’espace (SIP)) font également référence au type de page d’entretien. Ces pages sont situées à partir de la deuxième, c’est-à-dire que la première PIP vient juste après la page d’en-tête, et apparaissent dans une base de données à intervalles de pages fixes d’autres types. La taille de ces intervalles indique à quel nombre de pages d’autres types la PIP apparaît, et dépend de la taille de page définie pour cette base de données. Les pages d’inventaire de pages ne sont pas comptabilisées sur les pages pointeurs et ne sont pas signalées dans RDB$Pages. L’intégrité de ces pages est vitale pour le bon fonctionnement de toute la base, car le contenu de la PIP décrit l’état de toutes les autres pages de la base de données. Chaque page de base de données peut avoir 3 états : non allouée, allouée avec espace, allouée et pleine. Lorsqu’il y a un besoin d’espace supplémentaire pour de nouvelles données, le serveur vérifie la PIP afin de trouver des pages non allouées. S’il existe une telle page, le serveur modifie son état en allouée avec espace. S’il n’y a pas de pages non allouées, la base de données s’étend - une nouvelle page de données est ajoutée.
Un exemple de page de données dans IBSurgeon et les données qu’elle contient est donné dans l’image 5.

Image 5. Page d’inventaire de pages
Dès que la page est allouée, InterBase écrit son état sur la SIP puis écrit la page elle-même. Ensuite, nous devons ajouter cette page nouvellement formée à un grand nombre de pages, par exemple aux pages de données d’une table. Pour cela, nous devons écrire la référence à cette nouvelle page sur la dernière page de ce grand nombre de pages - par exemple, sur la dernière page de données d’une table. Si le serveur interrompt son travail juste après avoir écrit sur la SIP, mais sans écrire la référence sur les pages qui se réfèrent à la page juste allouée, alors cette page devient orpheline. Une page orpheline est physiquement créée, réservée sur la SIP, mais il n’y a aucune référence vers elle depuis d’autres pages, ce qui signifie que le serveur ne pourra pas la trouver et écrire des données sur le disque. La page orpheline est marquée par le carré rouge sur l’image 2. Les pages orphelines surviennent principalement à la suite d’une coupure de courant inattendue du serveur et sont « guéries » par l’outil spécial de réparation de base de données gfix (ou par FirstAID) (ou par IBSurFirstAID).
Avant d’examiner les pages de données, nous devons mentionner les types de pages importants : les pages générateur et les pages d’index. Les pages générateur représentent un tableau de nombres de 4 octets, montrant les états des générateurs. En réalité, le générateur est un compteur ordinaire.
Sur l’image 6, vous pouvez voir une page générateur. Notez que bien qu’IBSurgeon affiche les noms des générateurs, cela ne signifie pas que ces noms sont stockés sur les pages générateur. Cela est fait pour la commodité de l’utilisateur qui étudie la base de données. En réalité, les noms des générateurs sont stockés dans la table système RDB$Generators.

Image 6. Page générateur (g en-ids )
Comme vous le voyez dans cet exemple, la base de données contient des générateurs système, commençant par le préfixe RDB$, et des générateurs définis par l’utilisateur. Si vous voulez en savoir plus sur la fonction et l’utilisation des générateurs lors du développement d’applications de base de données InterBase, voir le chapitre « Tables. Clés primaires et générateurs ». Les pages générateur sont comptabilisées avec les autres pages dans la table RDB$Pages.
Chaque table possède au moins une page racine d’index, qu’elle ait ou non des index. Cette page contient des pointeurs vers les pages d’index pour une table donnée. Nous pouvons dire que la page racine d’index a la même importance pour les pages d’index que la page pointeur pour les pages de données. Par conséquent, IBSurgeon la représente de manière similaire. Un exemple de page racine d’index est donné dans l’image 7.

Image 7. Page racine d’index
La page racine d’index contient une liste de pages où les valeurs d’index sont stockées, ainsi que des informations sur l’index - la sélectivité de l’index et différents indicateurs. Pour plus de détails sur les index, leur rôle et leur utilisation dans les bases de données InterBase, voir le chapitre « Index ».
Les pages d’index contiennent directement les valeurs des index, ou si le niveau d’index > 0, des références aux pages d’index sous-jacentes. Voici un exemple de page d’index (image 8).

Image 8. Page d’index (B-tree)
La page d’index stocke des valeurs compressées des données indexées. Un mécanisme d’indexation assez complexe est utilisé, surtout lors de la création d’index composites (incluant plusieurs champs).
En général, les pages de données et les pages contenant des valeurs BLOB stockent des informations utilisateur. Les pages de données contiennent des enregistrements dans les tables utilisateur de la base de données, des fragments d’enregistrements, des anciennes versions, des différences entre versions, des champs BLOB, etc. Quant aux champs BLOB, ils sont liés aux enregistrements sur les pages de données et contiennent des données de grande taille qui ne peuvent pas être placées sur la page de données. Le type de référence pour le stockage des valeurs BLOB permet de stocker de grandes données.
Un exemple de présentation de page de données dans IBSurgeon est donné dans l’image 9 :

Image 9. Page de données
L’en-tête de la page de données contient le type de page, l’identifiant de la table propriétaire (relationID). Les enregistrements sont stockés sur les pages de données à partir de la fin de la page et sont alloués plus près du début de la page au fur et à mesure qu’ils sont remplis.
Nous pouvons nous en assurer si nous regardons les indices de lignes, contenant 2 valeurs - le décalage sur la page et sa longueur. Comme vous le voyez au début de la ligne, il y a des enregistrements alloués à la fin de la page - par exemple, le premier enregistrement a un décalage de 8156 octets et une longueur de 34 octets - il se termine donc à 8156+34=8192 octets - au bord même de la page (dans notre cas, la taille de page est de 8192 octets). Lorsque la page est remplie (avec des données depuis le haut et des indices d’enregistrements depuis le bas), le serveur commence à écrire de nouveaux enregistrements et des versions d’anciens enregistrements sur de nouvelles pages. À partir du mécanisme de remplissage de page décrit ci-dessus, nous pouvons facilement comprendre pourquoi les spécialistes d’InterBase recommandent vivement d’utiliser des pages de données de grande taille (4096 octets au minimum, mieux 8192). Si nous créons une table dont un enregistrement sera d’une taille assez grande (par exemple 10 champs de VARCHAR (255)), ils occuperont, une fois remplis, plus de 2550 octets. Cela signifie qu’un tel enregistrement sera trop grand pour une page de petite taille (1024 ou 2048). Il est évident qu’une nécessité de charger plusieurs pages depuis le disque pour lire un seul enregistrement n’accélérera pas le travail avec votre base de données. Il est donc recommandé de redéfinir la taille de la page de données lors de la création ou de la restauration d’une base de données, car la taille de 1024 octets est définie par défaut. Nous venons d’examiner brièvement les principaux types de pages des fichiers de données InterBase et leur fonction. Nous pouvons maintenant passer à un niveau structurel supérieur.
ODS
ODS est l’abréviation de On-Disk Structure, c’est-à-dire la structure des données de la base de données InterBase sur disque. L’ODS définit comment les données dans les fichiers de la base de données sont organisées. La définition des principales constantes et structures de données pour implémenter la structure sur disque se trouve dans le fichier ods.h de l’ensemble des codes sources InterBase. L’ODS a été modifié au cours du développement d’InterBase, et lors du travail avec une base de données concrète, le serveur découvre le numéro de version ODS pour savoir à quoi il a affaire. Le fichier ods.h nous présente les versions suivantes de la structure sur disque :
-
ODS 5 était utilisé par InterBase 3.3 et n’est pas supporté par les versions supérieures
-
ODS 6 et ODS 7 ne sont jamais sortis
-
ODS 8 est utilisé par InterBase 4.0
-
ODS 9 est utilisé par InterBase 4.5 et supérieur
-
ODS 10 est sorti avec InterBase 6
-
ODS 11 est sorti avec InterBase 7.0
Outre les versions ODS majeures, il existe des versions mineures qui dépendent d’une version concrète du serveur de base de données qui les a créées. Les numéros majeurs de la version sont écrits dans la partie entière du numéro, indiquant la version, les mineurs - dans la partie fractionnaire. Par exemple, la version du serveur 4.0 crée des bases de données ayant ODS 8.0 et InterBase 4.2 - 8.2. La transition entre les versions mineures de bas en haut est exécutée automatiquement. Par exemple, il suffit d’ouvrir une base avec ODS 8.0, créée par le serveur 4.0, avec InterBase 5.6, et l’ODS de cette base de données aura la version 8.2. La transition entre les versions majeures de la base de données est exécutée uniquement par la sauvegarde de la base de données, en utilisant une ancienne version, et la restauration, en utilisant une nouvelle version du serveur. Le processus de transition entre les versions est décrit en détail dans le chapitre 1.4 « Migration ».
Le point important dans l’implémentation du support ODS pour les versions InterBase 4.x et 5.x est une compatibilité ascendante des serveurs InterBase 4.x et 5.x avec la version d’une unité inférieure à l’implémentation d’un serveur concret. InterBase supporte plusieurs ODS possibles, et selon sa version ODS lors de la connexion à la base de données concrète, choisit le support de l’implémentation ODS requise. Le mécanisme de décision sur quelle implémentation de support ODS choisir dans un cas concret est appelé Y-Valve ((c) de Steve Trenton).
Plus simplement, une base de données avec ODS 8.x, correspondant à InterBase 4.0, peut être ouverte dans InterBase 5.x.
Le tableau complet de compatibilité ODS est présenté ci-dessous :
| Version InterBase | ODS majeur | ODS mineur |
| 4.0/4.1 | 8.0 | … |
| 4.2 | 8.2 | 8.2 |
| 5.0/5.1 | 9.0 | 8.2 |
| 5.5 | 9.1 | 8.2 |
| 5.6 | 9.1 | 8.2 |
| 6.0 | 10.0 | 9.0/9.1 |
| 7.0 | 11.0 | 10.0 |
| 7.1 | 11.1 | 10.0 |
L’ODS a une compatibilité descendante. En d’autres termes, un serveur avec une version supérieure et tous ses outils pourront travailler avec une base de données créée par des versions antérieures du serveur, mais pas l’inverse. Si vous essayez d’ouvrir une base de données créée dans la version 6 d’InterBase avec InterBase 5.x, vous recevrez un message d’erreur « Unsupported On-disk structure: Found ODS 10, supported ODS 9 ».
La description de la transition entre les versions de bas en haut et inversement se trouve dans le chapitre « Migration ».
L’ODS est très important pour les questions concernant la sauvegarde et l’extraction de la base de données, ainsi que la restauration des bases de données endommagées. Les outils de sauvegarde gbak et de restauration gfix surveillent la version ODS et ne fonctionneront tout simplement pas si la version ODS de la base de données qu’ils doivent servir est plus grande que la version implémentée en eux. Cela signifie que gbak de 4.x ne pourra pas créer une sauvegarde de base de données si elle est créée par le serveur 5.x, mais facilement l’inverse.
Un pont entre la structure physique et logique de la base de données
Nous avons considéré la structure physique des fichiers de base de données d’une manière générale. Maintenant, nous devons passer à la structure logique de la base de données. Faisons un pont entre les niveaux physique et logique de la représentation de l’information dans une base de données afin qu’il n’y ait pas de délimitation dans les notions et pas de lacunes dans le matériel. Tout ce qui est stocké dans différentes pages de base de données doit être organisé d’une certaine manière dans la mémoire de l’ordinateur ; les données du fichier de base de données doivent être converties en un ensemble d’objets et de variables intraserveur. Cet ensemble est appelé une image interne de la base de données selon la terminologie d’Ann Harrison [1.. Donc, nous allons essayer de considérer le processus de création de l’image interne de la base de données.
-
Le serveur lit 1024 octets depuis le début du fichier et, s’il s’agit réellement d’un fichier de base de données InterBase, il définit la taille de page de cette base et relit toute la page d’en-tête.
-
À partir de l’en-tête, le serveur de pages extrait le numéro de la page pointeur qui stocke les références aux pages de données, définissant ainsi la table RDB$Pages.
-
Le serveur se rend sur cette page pointeur et commence à lire les informations des pages de données pointées. Il remplit la première table RDB$Pages avec des données. Cette table est une sorte de pont entre les objets physiques - les pages des fichiers de base de données - et les objets logiques - les tables. La structure de RDB$Pages, comme celle des autres tables système, est strictement fixée dans InterBase.
-
Après avoir obtenu des données sur l’allocation des pages par relations (les relations - en fait, c’est la même chose que les tables ordinaires, et nous pouvons substituer mentalement ces notions pour simplifier), InterBase commence à former les structures de données : d’abord les tables système, les contraintes et les index, puis les objets utilisateur.
-
Après l’initialisation des métadonnées système et utilisateur (tables, contraintes, index et autres objets de base de données), InterBase retourne le handle de cette base de données à l’utilisateur qui a demandé d’ouvrir une base de données. En substance, le handle est un identifiant qui indique à InterBase avec quelle base de données travailler, car plusieurs utilisateurs peuvent travailler en même temps, ce qui signifie que plusieurs bases de données peuvent être ouvertes.
-
Après ces opérations, la base de données est considérée comme ouverte et le serveur est prêt à exécuter les requêtes utilisateur. Maintenant qu’un certain pont est établi, reliant la structure physique et logique de la base de données, nous pouvons commencer à étudier les particularités de la structure logique.
Structure logique de la base de données InterBase
La structure logique est une notion plutôt vague, c’est pourquoi nous allons essayer de maîtriser les idées clés progressivement, en espérant qu’elles deviendront plus tard intuitivement claires. La première chose que nous allons considérer concernant la structure logique de la base de données, ce sont les tables système et leur contenu. Les tables système décrivent le système, ainsi que les métadonnées utilisateur. D’une manière générale, le terme « métadonnées » signifie « données décrivant un ensemble de données ». Le préfixe « méta » signifie : « décrit un ensemble ». Par exemple, le métalangage est un langage décrivant un ensemble de langages. Les métadonnées décrivent les données utilisateur, c’est-à-dire les tables, les déclencheurs, les vues, les procédures stockées, etc. - tout ce qui implémente les règles de stockage et de traitement de l’information, pour lesquelles cette base de données concrète est créée.
Il est plutôt amusant de découvrir pour la première fois que toutes les métadonnées - tables utilisateur, déclencheurs, vues, ainsi que tous les objets système - sont stockées dans les mêmes tables, à partir desquelles vous pouvez lire et écrire des données par des requêtes SQL ordinaires. Ces tables ne diffèrent « visuellement » que par le fait que leurs noms commencent par RDB$. Ces 4 symboles sont réservés aux noms des objets système. Aucune table utilisateur, colonne ou autre objet n’a le droit d’avoir un nom commençant par ces symboles. Formellement, vous pouvez créer une table dont le nom commence par les symboles réservés, mais la documentation InterBase ne le recommande pas.
Une question se pose : si les données sur la structure de la base de données sont stockées dans les mêmes tables que les données utilisateur, où est alors stockée l’information sur les tables qui décrivent les tables ? Un exemple classique du problème de « la poule et l’œuf » - comment l’un pourrait-il apparaître avant l’autre, s’ils sont interdépendants ? La réponse est que les tables système dans leur état primitif sont fixées dans les codes initiaux d’InterBase et sont automatiquement ouvertes lors de la création d’une base de données dans un ordre défini. Nous avons déjà parlé de la table RDB$Pages qui compare les pages physiques des fichiers de base de données avec des objets définis de cette base. La structure de cette table est donnée ci-dessous :
Tableau 5. Table système RDB$Pages
| Nom de colonne | Type de données | Description |
| RDB$PAGE_NUMBER | INTEGER | Numéro de la page physique |
| RDB$RELATION_ID | SMALLINT | Identifiant de la table pour laquelle la page est allouée |
| RDB$PAGE_SEQUENCE | INTEGER | Numéro de cette page |
| RDB$PAGE_TYPE | SMALLINT | Type de page - voir tableau 3 |
Chaque page de données est liée à une certaine table. Cette relation est maintenue par le champ RDB$RELATION_ID, où une référence à la table est stockée. Comme décrit ci-dessus, lors de la construction de l’image interne de la base de données, le serveur crée cette table et la remplit avec des données selon un algorithme défini. Pour être précis, au moment de la construction de l’image interne de la base de données, RDB$Pages n’est pas une table, c’est juste un fichier de données d’un format défini, connu d’InterBase. Selon un algorithme fixe, le serveur lit les données de ce fichier et crée une table - RDB$Relations - qui est importante pour toute la base de données. Cette table décrit toutes les tables de la base de données. Si nous exécutons la requête SQL :
SELECT * from RDB$Relations
pour découvrir à quelles tables RDB$Relations fait référence, nous verrons qu’elle contient RDB$Pages et elle-même. Il est évident que dans ce cas, le serveur est un peu rusé, substituant ces tables système et d’autres dans RDB$Relations par rétro-datation, les légalisant ainsi. Il les enregistre comme des tables « normales », où il peut ajouter ou supprimer des enregistrements. En d’autres termes, il fournit une interface SQL standard pour travailler avec les métadonnées.
Et une question tout à fait raisonnable peut se poser - pourquoi les développeurs d’InterBase ajusteraient-ils leurs données système en fonction de l’interface utilisateur ? Vous voyez, les mécanismes internes d’accès et les opérations de lecture seraient plus rapides. Bien sûr, il y a un grand sens à fournir un mécanisme universel de travail avec les tables, décrivant les métadonnées.
Le fait est que la structure logique de la base de données ne se compose pas seulement de tables mais aussi d’autres objets. Il y a les objets suivants dans InterBase :
-
Table
-
Vue
-
Déclencheur
-
Champ_calculé
-
Validation
-
Procédure
-
Index_d’expression
-
Exception
-
Utilisateur
-
Champ
-
Index
-
Fonction définie par l’utilisateur (UDF)
Nous ne connaissons pas encore avec certitude la fonction de certains objets, mais nous savons avec certitude que tous doivent être décrits et stockés d’une manière pratique pour l’utilisateur et pour l’accès depuis le noyau InterBase. Le mieux serait de sauvegarder ces objets dans des tables système. Leur ajout et leur modification sont effectués par des requêtes SQL. Une solution intelligente, n’est-ce pas ? L’implémentation du serveur est complètement séparée d’une base de données concrète - toutes les interconnexions sont décrites par SQL et ses extensions - le langage des procédures stockées et des déclencheurs.
Ainsi, tous les objets du serveur sont stockés dans des tables. Chaque type d’objet a une table décrivant toutes les instances décrites dans la base de données. Par exemple, pour les déclencheurs, il y a une table RDB$Triggers, pour les procédures stockées - RDB$Procedures, les vues sont décrites dans la table RDB$Relations.
Considérons en détail la structure de cette dernière table, décrivant toutes les tables et vues d’une base de données. La structure de la table RDB$RELATIONS est tirée de Language Reference pour InterBase 6 et est donnée ci-dessous dans le tableau 6.
Tableau 6. Table système RDB$Relations
| Nom de colonne | Type de données | Longueur | Description |
| RDB$VIEW_BLR | BLOB | 80 | BLR : pour les vues, contient le BLR (Binary Language Representation) de la requête, qu’InterBase exécute à chaque fois qu’on fait appel à la vue. |
| RDB$VIEW_SOURCE | BLOB | 80 | Texte : pour les vues, contient le code de la requête SQL qui implémente cette vue. |
| RDB$_DESCRIPTION | BLOB | 80 | Description utilisateur de la table ou de la vue |
| RDB$RELATION_ID | SMALLINT | Contient l’identifiant interne de la table/vue | |
| RDB$SYSTEM_FLAG | SMALLINT | Définit le type de table : données utilisateur - 0 ; informations système > 0. | |
| RDB$DBKEY_LENGTH | SMALLINT | Longueur de db$key | |
| RDB$FORMAT | SMALLINT | Réservé à l’usage interne d’InterBase. Contient le compteur de modification des métadonnées pour la table donnée. | |
| RDB$FIELD_ID | SMALLINT | Le nombre de champs dans la table. | |
| RDB$RELATION_NAME | CHAR | 31 | Nom unique de la table. |
Dans la description de cette table système, nous voyons une abréviation BLR. Pour comprendre ce que c’est, nous allons faire une excursion dans SQL. Comme on le sait, les vues, les déclencheurs et les procédures stockées sont un code écrit dans une extension du langage SQL (pour chaque SGBD, il y a leurs propres extensions). Il est proche du langage humain, ce qui permet de faire des requêtes facilement. Mais InterBase, évidemment, le traduit en quelque chose de plus « machine » - à savoir en BLR (Binary Language Representation). Toute requête, vue, déclencheur, procédure stockée est toujours traduite en BLR puis transmise au noyau InterBase pour exécution.
BLR
BLR est un langage spécial, utilisé comme lien intermédiaire entre le code SQL qu’un programmeur écrit et le code machine que le serveur admet. Personne n’écrit directement en BLR - ce serait plutôt difficile car pour une vitesse d’exécution maximale, ce langage utilise ce qu’on appelle la notation polonaise inversée. Voici un petit exemple :
blr_begin,
blr\_assignment,
blr\_field, 0, 7, 'D','A','T','E','I','Z','M',
blr\_variable, 1,0,
blr\_assignment,
blr\_field, 0, 4, 'R','A','T','E',
blr\_variable, 0,0,
blr\_block,
Le BLR pour vos requêtes, procédures, déclencheurs et autres déclencheurs est formé par le préprocesseur spécial qui fait partie du noyau du serveur. Comme le montre le tableau 7, pour les vues, leur texte (initial) de vue, ainsi que la vue compilée, c’est-à-dire le BLR, sont stockés. Lorsqu’on fait référence à un objet ayant un BLR, le serveur exécute le code binaire de l’objet, et il n’interprète pas le texte initial de ces objets à chaque fois, ce qui permet d’accélérer l’exécution des requêtes compliquées.
Hiérarchie des objets dans InterBase
Pour avoir une idée claire de ce que représentent les objets de base de données, nous allons essayer de faire une hiérarchie des objets de base de données selon le principe « qui contient quoi ». Les pages physiques des fichiers de base de données sont les premières à devoir être incluses dans notre hiérarchie comme le niveau le plus bas de l’organisation des données. Ensuite, les tables vont comme objets de base, décrivant tous les autres types d’objets. Les tables décrivent les procédures stockées, les déclencheurs, les champs calculés, les validations, les index d’expression, les exceptions, etc. Attention - seulement décrivent ! Les tables ne contiennent que des déclarations et des définitions de ces objets, et les objets sont implémentés via BLR. Par conséquent, nous pouvons représenter les tables sous la forme d’un cadre, soutenant tous les autres objets de la base de données. Le BLR sera en bas du cadre comme couche d’implémentation, puis les déclencheurs, les procédures stockées, les index d’expression et les vues.
Pour calmer les spécialistes de la structure interne d’InterBase, qui pourraient objecter que le BLR de nombreux objets (tels que les vues) est stocké dans des tables système, nous commenterons que cette attitude est plutôt difficile à exprimer sur une image, et pour simplifier, nous la mettons de côté. Le schéma n’a pas pour but de recréer les interdépendances des objets de base de données de manière absolument précise ; il illustre juste leur étroite interconnexion.
Le fait que ces types d’objets soient directement connectés au BLR, qui les implémente sans aucune logique intermédiaire, les unit. Les exceptions doivent être allouées séparément - elles représentent des types spéciaux d’erreurs, définis par l’utilisateur. Les exceptions sont traitées au niveau du noyau InterBase et n’ont donc pas de BLR. De tels types de contraintes comme les checks sont alloués au-dessus des déclencheurs car en réalité les déclencheurs implémentent la logique des contraintes et des checks.
Une hiérarchie des objets de la structure logique et physique de la base de données est peinte sur l’image 2.
Image 10. Objets de la structure logique de la base de données InterBase
Bien sûr, ce schéma décrit la structure logique et les interconnexions des objets dans la base de données seulement approximativement et donne une idée générale de celle-ci. Quiconque veut étudier la structure des métadonnées de la base de données InterBase peut exécuter un réingénierie des tables système de la base de données et considérer toutes les interconnexions entre ses objets, ainsi que consulter la documentation et les codes primaires d’InterBase. Ce tableau ne montre que les principaux objets de la base de données. Décrivons brièvement les principales fonctions que ces objets remplissent dans la base de données.
Tables - l’objet principal, contenant les données utilisateur et système. Une table a un nom unique et contient un ensemble de champs nommés. Un utilisateur peut placer des données, extraire et modifier des données dans les tables. On peut dire qu’une table est similaire aux tables papier ordinaires dessinées à la main.
Triggers - parties exécutables du code, utilisées pour implémenter des actions supplémentaires au moment des opérations sur les données. Les triggers sont exécutés avant ou après les opérations d’insertion, de modification ou de suppression, et permettent de réaliser la substitution de valeurs dans les enregistrements nouvellement créés et bien d’autres choses.
Une procédure stockée est un outil puissant pour implémenter la logique métier au niveau de la base de données. Étant exécutée au niveau du serveur, elle fonctionne très rapidement et permet d’exécuter un ensemble d’opérations sur des ensembles de données. Les procédures stockées InterBase renvoient des ensembles de données SQL standard, sur lesquels toutes les opérations SQL, y compris l’unification avec d’autres tables, sont possibles.
Les vues sont des requêtes SQL compilées, exécutées sur le serveur. Les vues permettent d’organiser des ensembles de données, transférant une partie de la logique métier sur le serveur.
Les validations sont des contraintes définies sur les valeurs des champs de la table. Par exemple, nous pouvons indiquer que le champ donné n’acceptera que des valeurs positives. Les contraintes sur les valeurs des champs sont implémentées par des triggers et permettent de contrôler efficacement l’intégrité référentielle au niveau de la base de données. Généralement, les contraintes sont utilisées pour empêcher qu’une table reçoive des valeurs incorrectes.
Utilisateurs - InterBase nous permet d’avoir plusieurs utilisateurs pour travailler avec la base de données et de répartir entre eux les droits d’accès aux différents objets de la base de données. Ainsi, nous pouvons contrôler les autorisations pour ces opérations ou ces opérations de base de données.
Fonctions définies par l’utilisateur (UDF) - fonctions, définies par l’utilisateur. C’est l’une des capacités les plus puissantes d’InterBase, nous permettant d’étendre une interface SQL standard par nos propres fonctions. Par exemple, les fonctions de travail avec les chaînes telles que UPPER (mettant tous les symboles en majuscules), sont implémentées dans la bibliothèque UDF standard, incluse dans l’ensemble InterBase. Grâce à la possibilité de créer ses propres UDF, les développeurs peuvent étendre la fonctionnalité d’InterBase par pratiquement n’importe quelles fonctions. Nous pouvons utiliser n’importe quel environnement de programmation permettant de créer des bibliothèques dynamiques (Visual C++, C++ Builder, Delphi, etc.) pour créer des UDF.
Conclusion
Dans ce chapitre, nous avons examiné pour la première fois les questions concernant l’implémentation du stockage et du traitement des données dans une base de données InterBase. Malheureusement, nous ne pouvons pas faire une brève revue de ce thème sans recourir à un grand nombre de termes et d’analogies imprécises. Si nous décrivions la structure physique et logique de la base de données plus en détail, nous devrions de toute façon nous référer aux codes primaires d’InterBase, mais ce serait un autre livre.
Néanmoins, nous pensons qu’il serait utile pour chaque programmeur de se familiariser avec le contenu du produit qu’il utilise tous les jours.
Bibliographie
-
«The On-Disk Structure of InterBase» par Ann.W.Harrison
-
«Space Management in InterBase» par Ann W.Harrison
-
«Structure of a Data Page» par Paul Beach (Avec remerciements à Dave Schnepper et Deej Bredenberg)