Struttura fisica del database (InterBase e Firebird)
Alexey Kovyazin, Sergey Vostrikov, ultimo aggiornamento 05-giugno-2004
Struttura fisica del database
Perché dobbiamo studiare la struttura fisica del database InterBase?
Di solito, quando parliamo della struttura fisica del database InterBase, intendiamo che essa rappresenta i dati dal punto di vista dell’organizzazione dei dati a basso livello - fino al livello dei byte. Molti programmatori che sviluppano applicazioni usando linguaggi ad alto livello trascurano lo studio dei dettagli di basso livello. Tuttavia, conoscere i principi principali dell’organizzazione dei dati all’interno del database è la chiave per una progettazione efficace delle applicazioni di database. Pertanto faremo un’escursione all’interno dell’organizzazione del database InterBase e scopriremo come è strutturato.
Quindi, a cosa serve un sistema di gestione di database (DBMS)? Ovviamente per memorizzare e controllare i dati. Sembra banale, ma vale la pena pensarci. Un utente inserisce i dati nel DBMS, che in qualche modo traduce questi dati in formati interni comprensibili. Puoi immaginare “0 e 1” se le parole “formato dati interno” causano qualche difficoltà con le associazioni. Il DBMS memorizza questi dati e, al momento della prima richiesta, deve estrarli dal suo formato, convertirli in una vista adatta e fornirli all’utente.
L’argomento di questo capitolo è come il DBMS memorizza i suoi dati, in quale vista e come sono organizzati al livello più basso. Cercheremo di spiegarti come da bit e byte, che giacciono sull’HDD, otteniamo dati preziosi.
File del database InterBase
Di solito, quando parliamo di un database intendiamo il DBMS stesso e le informazioni utente, e persino i programmi dei client che lavorano con i dati. In questo capitolo, considereremo un database come file di database.
Il database InterBase rappresenta uno o più file contenenti informazioni su tutto ciò che è connesso a questa base. Le informazioni sugli utenti sono un’eccezione perché gli utenti sono definiti a livello dell’intero server e sono memorizzati separatamente, nel database di sicurezza admin.ib (era ISC4.GDB nelle versioni precedenti alla 7).
Consiglio: Guarda il capitolo “Sicurezza del server e del database” per studiare di più sui principi di sicurezza di InterBase.
Quindi, tutte le informazioni sul database sono memorizzate all’interno di questi file: dati stessi, indici, trigger, procedure memorizzate ecc.
Il database InterBase per un progetto medio rappresenta un singolo file perché le versioni moderne di InterBase possono usare IO a 64 bit per operare con il file di dati e questo ti dà la capacità di avere un file di dati fino a 64 GB. Le versioni precedenti di InterBase avevano il vincolo di 4 gigabyte per ogni file di database (fino a 64 Tbyte per l’intero database). Come possiamo supporre, 64 gigabyte sono abbastanza per memorizzare informazioni di quasi qualsiasi applicazione di database. Ma se necessario, possiamo dividere un database in più file. A proposito, ci sono database InterBase di dimensioni di centinaia di gigabyte.
IBSurgeon - una guida attraverso il database InterBase
Dobbiamo conoscere in dettaglio la struttura dei file del database InterBase. E quindi è desiderabile avere uno strumento conveniente che permetta di lavorare direttamente con i file di database, non tramite il kernel del server InterBase. Il modo più semplice è usare un normale visualizzatore esadecimale e cercare di capire la struttura dei file di database considerando la sua rappresentazione HEX. Sarebbe un lavoro piuttosto noioso.
Ma fortunatamente, c’è uno strumento per il lavoro diretto con i database InterBase. È IBSurgeon Editor - uno strumento per il lavoro diretto a basso livello con i database InterBase, che può essere usato per studiare la struttura interna dei database InterBase e diagnosticare database corrotti al fine di ripristinarli. Per maggiori dettagli vedi appendice “Strumenti per amministratori e sviluppatori InterBase”.
IBSurgeon usa il proprio meccanismo alternativo di accesso al database che permette di aprire e rivedere database in qualsiasi stato, inclusi quelli gravemente corrotti che non possono essere aperti dal kernel del server InterBase/FireBird/Yaffil.
Useremo IBSurgeon per illustrare la struttura interna del database.
File *.IB/*.FDB dall’interno
IB è un’estensione, raccomandata per i file di database InterBase, e FDB per Firebird (precedentemente era GDB). La prima cosa che dobbiamo dire sulla struttura del file IB è che rappresenta un insieme di pagine di dimensione strettamente definita. La dimensione del file di database è divisibile per la dimensione della pagina, che è invariata per tutti i file di questo database. Diverse versioni di InterBase supportano diverse dimensioni di pagina, come mostrato nella tabella 1. La dimensione della pagina è impostata quando si crea un database e non può essere cambiata durante il suo ciclo di vita. In altre parole, possiamo cambiare la dimensione della pagina solo quando si ripristina un database da un backup.
Tabella 1. Dimensioni di pagina supportate da diverse versioni di InterBase
| Versione InterBase | Dimensione pagina, byte | ||||
| 1024 | 2048 | 4096 | 8192 | 16384 | |
| InterBase 4.0 | * | * | * | * | |
| InterBase 5.x | * | * | * | * | |
| InterBase 6.x-7.x | * | * | * | * |
La lettura e la scrittura dei dati in un database sono eseguite pagina per pagina, molte importanti caratteristiche del server e del database come la dimensione della cache del database dipendono dalla dimensione della pagina e sono contate in “pagine”.
Apriamo qualsiasi database InterBase con IBSurgeon. È sufficiente fare doppio clic sul file di database. L’immagine 1 esprime un elenco di pagine che appare dopo che IBSurgeon ha aperto il database:

Immagine 1. Un elenco di pagine del database
Le pagine possono essere di diversi tipi, ognuno dei quali serve a un certo scopo. Le interdipendenze dei diversi tipi sono rappresentate condizionatamente nell’immagine 2. L’immagine 2 rappresenta schematicamente un’allocazione delle pagine nel file di database - da sinistra a destra, dall’alto verso il basso, se si conta dall’inizio del file. Le pagine dello stesso tipo non vanno strettamente una dopo l’altra - possono essere facilmente mescolate, allocate in un file nell’ordine in cui sono state create dal server quando si estende o si crea un database.

Immagine 2. Interdipendenze tra diversi tipi di pagine nel database InterBase
Devi aver notato che alcuni tipi di pagine non hanno riferimenti ad altri tipi di pagine. Tuttavia, non c’è contraddizione qui, il fatto è che questi tipi di pagine sono collegati e usati a un altro livello strutturale. Possono essere collegati alla tabella RDB$PAGES e ad altre tabelle di sistema (questa tabella e altri oggetti di sistema li considereremo sotto - nel capitolo “Struttura logica del database”). Nell’immagine 2 possiamo vedere solo riferimenti espliciti tra le pagine a livello fisico.
Consideriamo in dettaglio quali tipi di pagine ci sono nel database InterBase. Nel file ods.h dall’insieme dei codici primari di InterBase ci sono informazioni su tutti i possibili tipi di pagine. Faremo spesso riferimento a questo file per ricevere i dati non solo su ODS ma anche su molte altre cose fondamentali del kernel InterBase nella fonte originale. In tutto sono dichiarati 11 tipi di pagine, ma solo 9 di essi meritano di essere spiegati (possiamo vederlo chiaramente dalla tabella 2). I tipi di pagina con identificatori 0 e 1 sono indefiniti o non usati.
Tabella 3. Tipi di pagina in FB
| La definizione in ods.h | Identificatore del tipo di pagina | Descrizioni della pagina |
| pag_undefined | 0 | Indefinita - Se una pagina ha questo tipo di pagina è probabilmente libera |
| pag_header | 1 | Pagina di intestazione del database |
| pag_pages | 2 | Pagina di inventario delle pagine (o pagina di inventario dello spazio - SIP) |
| pag_transactions | 3 | Pagina di inventario delle transazioni (TIP) |
| pag_pointer | 4 | Pagina puntatore |
| pag_data | 5 | Pagina dati |
| pag_root | 6 | Pagina radice dell’indice |
| pag_index | 7 | Pagina indice (B-tree) |
| pag_blob | 8 | Pagina dati Blob |
| pag_ids | 9 | Gen-ids |
| pag_log | 10 | Informazioni di write ahead log |
Ogni pagina ha la sua intestazione, contenente informazioni sul tipo di pagina e un numero della pagina successiva dello stesso tipo. Possiamo ottenere l’elenco completo dei parametri che ogni intestazione di pagina contiene, se consideriamo la struttura pag nel file di definizioni ods.h.
/\* Intestazione di pagina di base */
typedef struct pag {
SCHAR pag_type; /*identificatore del tipo di pagina*/
SCHAR pag_flags; /*flag della pagina*/
USHORT pag_checksum; /*checksum della pagina: è uguale a 12345 dopo la versione 5.0 */
ULONG pag_generation; /*generazione della pagina */
ULONG pag_seqno; /* WAL seqno dell’ultimo aggiornamento - deprecato*/
ULONG pag_offset; /* WAL offset dell’ultimo aggiornamento - deprecato*/
} *PAG;
Tipi di pagina e loro utilizzo
Consideriamo ogni tipo di pagina in dettaglio e conosciamo la loro funzione e le informazioni che contengono. Inizieremo passo dopo passo - dalla prima pagina.
Qualsiasi operazione con un database inizia dalla lettura della pagina di intestazione del database (o pagina di intestazione). La pagina di intestazione del database va per prima in tutti i file di database. Di conseguenza, è rappresentata per prima nell’immagine 2 (se immaginiamo che l’immagine rappresenti un’estensione del file di database da sinistra a destra, dall’alto verso il basso).
Una pagina di intestazione contiene informazioni sul database nel suo insieme. Nell’immagine 3 una pagina dati è espressa nel modo in cui IBSurgeon ce la mostra:

Immagine 3. Pagina di intestazione del database.
Puoi farti un’idea del contenuto della pagina di intestazione, avendo ricevuto le statistiche del database. Per questo, puoi usare l’utilità della riga di comando gstat o un altro strumento più conveniente per l’amministrazione di InterBase dall’elenco nell’applicazione “Strumenti per amministratori e sviluppatori InterBase”. Per maggiori dettagli sul processo di ricezione delle statistiche e descrizione dell’intestazione, i dati della pagina vedi capitolo “Statistiche”.
Va notato che la pagina di intestazione contiene informazioni importanti come la dimensione della pagina, il numero della versione ODS (informazioni su di esso troverai sotto), i dati di creazione del database, informazioni sulle transazioni e un insieme di diverse informazioni. Per esempio, Implementation ID memorizza informazioni su sotto quale sistema operativo questo database è stato creato.
Quando si connette a un database, il server InterBase legge i primi 1024 byte di informazioni dall’inizio del file e definisce secondo i valori letti se il file indicato nella linea di connessione è un database InterBase o no. Poi il server legge il numero della versione ODS da una pagina di intestazione e la dimensione della pagina in questo database e, se la versione ODS è compatibile con l’implementazione del server, rilegge l’intera pagina di intestazione, usando la dimensione di pagina appropriata, ricevuta dai primi 1024 byte. Dopo di che, il resto dei parametri importanti del database come la modalità di lettura-scrittura, il dialetto del database ecc. sono letti dalla pagina di intestazione.
Sulla pagina di intestazione c’è un riferimento alla prima delle pagine puntatore, che memorizzano riferimenti alle pagine dati che contengono metadati: la tabella RDB$Pages (vedi sotto nel capitolo “Struttura logica del database InterBase”). Nell’immagine 2 questo riferimento è illustrato da una freccia con l’iscrizione “Numero della 1ª pagina puntatore nel database”. Il server legge il numero della 1ª pagina puntatore dalla pagina di intestazione e procede ad essa. La pagina puntatore consiste in un array ordinato di numeri di pagine dati che compongono una certa tabella (una tabella è considerata come oggetto SQL, descritto dalla struttura logica del database). Ora puoi vedere come IBSurgeon interpreta la pagina puntatore (guarda l’immagine 4):

Immagine 4. Pagina puntatore del database InterBase
La pagina contiene un vettore di pagine dati; questi dati compongono una certa tabella in un database. Questo vettore rappresenta un array di puntatori corrispondenti ai numeri delle pagine dati nel file. Il server legge un numero di 4 byte della pagina dati e procede a una necessaria pagina dati. Quando procede alla 1ª pagina dati di RDB$Pages, il server inizia a costruire la rappresentazione interna del database che è usata successivamente dal server per tutte le operazioni con il database. RDB$Pages memorizza riferimenti non solo alle pagine dati contenenti informazioni sul database ma anche alle restanti pagine che svolgono un ruolo nel fornire il lavoro del database.
Menzioniamo spesso questa tabella che strettamente parlando appartiene alla struttura logica del database. Tuttavia, tutto è interconnesso, quindi non possiamo descrivere qualcosa senza riferirci a qualcos’altro.
Uno dei tipi di pagina importanti è la pagina di inventario delle transazioni (TIP). Queste pagine, come tutte le pagine, sono costituite da un’intestazione e da una parte principale che rappresenta un array di sequenze di 2 byte. Le sequenze descrivono lo stato delle transazioni in un database (per maggiori dettagli sulle transazioni vedere il capitolo «Transazioni»).
Tabella 4. Possibili stati delle transazioni in TIP
| Valore della sequenza su PIP | Significato |
| 0 | La transazione non è iniziata, è attiva o è andata persa senza commit o rollback |
| 1 | La transazione ha eseguito il Commit |
| 2 | La transazione ha eseguito il Rollback |
| 3 | Transazione in limbo (per 2PC) |
Ogni versione di record ha il proprio identificatore di transazione, il che consente alle transazioni eseguite simultaneamente di «conoscere» lo stato reciproco e di risolvere i conflitti durante il lavoro multiutente (vedere il capitolo “Architettura multi-generazionale di InterBase” per saperne di più sulle versioni dei record e altri aspetti).
La pagina di intestazione del database, le pagine puntatore e la TIP appartengono ai tipi di pagina di «manutenzione», utilizzati solo dal server. Gli utenti di InterBase non ottengono mai esplicitamente le informazioni che contengono. Anche le pagine che memorizzano informazioni sull’allocazione delle pagine (di solito indicate come Page Inventory Pages (PIP) o Space Inventory Pages (SIP)) appartengono al tipo di pagina di manutenzione. Queste pagine si trovano a partire dalla seconda, cioè la prima PIP si trova subito dopo la pagina di intestazione, e compaiono in un database a intervalli fissi di pagine di altri tipi. La dimensione di questi intervalli indica dopo quante pagine di altri tipi appare la PIP e dipende dalla dimensione della pagina impostata per questo database. Le Page Inventory Pages non sono contabilizzate nelle pagine puntatore e non sono indicate in RDB$Pages. L’integrità di queste pagine è fondamentale per il corretto funzionamento dell’intero database, perché il contenuto della PIP descrive lo stato di tutte le altre pagine del database. Ogni pagina del database può avere 3 stati: non allocata, allocata con spazio, allocata e piena. Quando c’è necessità di spazio aggiuntivo per nuovi dati, il server controlla la PIP per trovare pagine non allocate. Se esiste una tale pagina, il server ne modifica lo stato in allocata con spazio. Se non ci sono pagine non allocate, il database si espande - viene aggiunta una nuova pagina dati.
Un esempio di pagina dati in IBSurgeon e i dati che contiene è mostrato nell’immagine 5.

Immagine 5. Page Inventory Page
Non appena la pagina viene allocata, InterBase scrive il suo stato sulla SIP e poi scrive la pagina stessa. Dopo di che, dobbiamo aggiungere questa pagina appena formata a un grande numero di pagine, ad esempio alle pagine dati di una tabella. Per farlo, dobbiamo scrivere il riferimento a questa nuova pagina sull’ultima pagina di questo grande numero di pagine - ad esempio, sull’ultima pagina dati di una tabella. Se il server interrompe il suo lavoro subito dopo aver scritto sulla SIP, ma senza scrivere il riferimento sulle pagine che si riferiscono alla pagina appena allocata, allora questa pagina diventa orfana. Una pagina orfana è fisicamente creata, riservata sulla SIP, ma non ci sono riferimenti ad essa da altre pagine, il che significa che il server non riuscirà a trovarla e a scrivere dati su disco. La pagina orfana è contrassegnata dal quadrato rosso nell’immagine 2. Le pagine orfane sorgono principalmente a seguito di un’improvvisa interruzione di corrente del server e vengono «curate» dallo strumento speciale per la riparazione del database gfix (o da FirstAID) (o da IBSurFirstAID).
Prima di considerare le pagine dati, dobbiamo menzionare tipi di pagina importanti: pagine generatore e pagine indice. Le pagine generatore rappresentano un array di numeri a 4 byte, che mostrano gli stati dei generatori. In realtà, il generatore è un normale contatore.
Nell’immagine 6 potete vedere una pagina generatore. Prestate attenzione al fatto che, sebbene IBSurgeon mostri i nomi dei generatori, questi nomi non sono memorizzati nelle pagine generatore. Questo è fatto per comodità dell’utente che studia il database. In realtà, i nomi dei generatori sono memorizzati nella tabella di sistema RDB$Generators.

Immagine 6. Pagina generatore (g en-ids )
Come si vede in questo esempio, il database contiene generatori di sistema, che iniziano con il prefisso RDB$, e generatori definiti dall’utente. Se volete sapere di più sulla funzione e l’uso dei generatori nello sviluppo di applicazioni di database InterBase, vedere il capitolo «Tabelle. Chiavi primarie e generatori». Le pagine generatore sono contabilizzate insieme alle altre pagine nella tabella RDB$Pages.
Ogni tabella ha almeno una pagina radice dell’indice, indipendentemente dal fatto che abbia o meno indici. Questa pagina contiene puntatori alle pagine indice per una determinata tabella. Possiamo dire che la pagina radice dell’indice ha la stessa importanza per le pagine indice che la pagina puntatore ha per le pagine dati. Pertanto IBSurgeon la rappresenta in modo simile. Un esempio di pagina radice dell’indice è mostrato nell’immagine 7.

Immagine 7. Pagina radice dell’indice
La pagina radice dell’indice contiene un elenco di pagine in cui sono memorizzati i valori dell’indice, nonché informazioni sull’indice - selettività dell’indice e vari flag. Per maggiori dettagli sugli indici, il loro ruolo e utilizzo nei database InterBase vedere il capitolo «Indici».
Le pagine indice contengono direttamente i valori degli indici o, se il livello dell’indice >0, riferimenti alle pagine indice sottostanti. Ecco un esempio di pagina indice (immagine 8).

Immagine 8. Pagina indice (B-tree)
La pagina indice memorizza valori compressi dei dati indicizzati. Viene utilizzato un meccanismo di indicizzazione piuttosto complesso, specialmente quando si creano indici composti (che includono più campi).
In generale, le pagine dati e le pagine che contengono valori BLOB memorizzano informazioni utente. Le pagine dati contengono record nelle tabelle utente del database, frammenti di record, versioni precedenti, differenze tra versioni, campi BLOB e così via. Per quanto riguarda i campi BLOB, essi sono collegati ai record nelle pagine dati e contengono dati di grandi dimensioni che non possono essere collocati nella pagina dati. Il metodo di riferimento per la memorizzazione dei valori BLOB consente di archiviare grandi quantità di dati.
Un esempio di presentazione di una pagina dati in IBSurgeon è mostrato nell’immagine 9:

Immagine 9. Pagina dati
L’intestazione della pagina dati contiene il tipo di pagina e l’identificatore della tabella proprietaria (relationID). I record sono memorizzati nelle pagine dati a partire dalla fine della pagina e vengono allocati più vicino all’inizio della pagina man mano che vengono riempiti.
Possiamo verificarlo osservando gli indici di riga, che contengono 2 valori - l’offset nella pagina e la sua lunghezza. Come si vede all’inizio della riga ci sono record allocati alla fine della pagina - ad esempio, il primo record ha un offset di 8156 byte e una lunghezza di 34 byte - quindi termina a 8156+34=8192 byte - proprio al limite della pagina (nel nostro caso la dimensione della pagina è 8192 byte). Quando la pagina è piena (con dati dall’alto e indici di riga dal basso), il server inizia a scrivere nuovi record e versioni di vecchi record su nuove pagine. Dal meccanismo di riempimento della pagina descritto sopra, possiamo facilmente capire perché gli specialisti di InterBase raccomandano vivamente di utilizzare pagine dati di grandi dimensioni (al minimo 4096 byte, meglio 8192). Se creiamo una tabella in cui un record sarà di dimensioni piuttosto grandi (ad esempio 10 campi di VARCHAR (255)), occuperà, una volta riempito, più di 2550 byte. Ciò significa che tale record sarà troppo grande per una pagina di piccole dimensioni (1024 o 2048). È ovvio che la necessità di caricare più pagine dal disco per leggere un singolo record non accelererà il lavoro con il database. Pertanto si raccomanda di ridefinire la dimensione della pagina dati quando si crea o si ripristina un database, poiché la dimensione di 1024 byte è impostata per impostazione predefinita. Abbiamo appena considerato brevemente i principali tipi di pagine dei file dati di InterBase e la loro funzione. Ora possiamo passare a un livello strutturale superiore.
ODS
ODS è l’abbreviazione di On-Disk Structure, cioè la struttura dei dati del database InterBase su disco. ODS definisce come sono organizzati i dati all’interno dei file del database. La definizione delle costanti principali e delle strutture dati per l’implementazione della struttura su disco si trova nel file ods.h dell’insieme dei codici sorgente di InterBase. L’ODS è stato modificato durante il processo di sviluppo di InterBase e, quando si lavora con un database concreto, il server rileva il numero della versione ODS per sapere con cosa ha a che fare. Il file ods.h ci presenta le seguenti versioni della struttura su disco:
-
ODS 5 è stato utilizzato da InterBase 3.3 e non è supportato dalle versioni successive
-
ODS 6 e ODS 7 non sono mai state rilasciate
-
ODS 8 è utilizzata da InterBase 4.0
-
ODS 9 è utilizzata da InterBase 4.5 e successive
-
ODS 10 è stata rilasciata con InterBase 6
-
ODS 11 è stata rilasciata con InterBase 7.0
Oltre alle versioni ODS principali, esistono versioni minori che dipendono dalla versione concreta del server di database che le ha create. I numeri principali della versione sono scritti nella parte intera del numero, che indica la versione, quelli minori - nella parte frazionaria. Ad esempio, la versione del server 4.0 crea database con ODS 8.0 e InterBase 4.2 - 8.2. Il passaggio tra versioni minori dal basso verso l’alto viene eseguito automaticamente. Ad esempio, è sufficiente aprire un database con ODS 8.0, creato dal server 4.0, con InterBase 5.6, e l’ODS di questo database avrà versione 8.2. Il passaggio tra versioni principali del database viene eseguito solo tramite backup del database, utilizzando una versione precedente, e ripristino, utilizzando una nuova versione del server. Il processo di transizione tra versioni è descritto in dettaglio nel capitolo 1.4 «Migrazione».
Il punto importante nell’implementazione del supporto ODS per le versioni di InterBase 4.x e 5.x è la compatibilità all’indietro dei server InterBase 4.x e 5.x con una versione inferiore di un’unità rispetto all’implementazione di un server concreto. InterBase supporta diverse ODS possibili e, in base alla sua versione ODS quando si connette a un database concreto, sceglie il supporto dell’implementazione ODS richiesta. Il meccanismo per decidere quale implementazione del supporto ODS scegliere in un caso concreto è chiamato Y-Valve ((c) di Steve Trenton).
In parole povere, un database con ODS 8.x, corrispondente a InterBase 4.0, può essere aperto in InterBase 5.x.
La tabella completa di compatibilità ODS è mostrata di seguito:
| Versione InterBase | ODS principale | ODS minore |
| 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 ha compatibilità verso il basso. In altre parole, un server con versione superiore e tutti i suoi strumenti riusciranno a lavorare con un database creato da versioni precedenti del server, ma non il contrario. Se si tenta di aprire un database creato nella versione 6 di InterBase con InterBase 5.x, si riceverà un messaggio di errore «Unsupported On-disk structure: Found ODS 10, supported ODS 9».
La descrizione della transizione tra versioni dal basso verso l’alto e viceversa è disponibile nel capitolo «Migrazione».
L’ODS è molto importante per le questioni relative al backup e all’estrazione del database, nonché al ripristino di database danneggiati. Gli strumenti di backup gbak e ripristino gfix controllano la versione ODS e semplicemente non funzioneranno se la versione ODS del database che devono servire è più grande della versione implementata in essi. Ciò significa che gbak di 4.x non sarà in grado di creare un backup del database se questo è stato creato dal server 5.x, mentre è facile il contrario.
Un ponte tra la struttura fisica e logica del database
Abbiamo considerato in modo generale la struttura fisica dei file del database. Ora dobbiamo passare alla struttura logica del database. Creiamo un ponte tra i livelli fisico e logico della rappresentazione delle informazioni in un database, affinché non ci siano separazioni nei concetti e lacune nel materiale. Tutto ciò che è memorizzato nelle diverse pagine del database deve essere organizzato in qualche modo nella memoria del computer; i dati dal file del database devono essere convertiti in un insieme di oggetti e variabili intraserver. Questo insieme è chiamato immagine interna del database secondo la terminologia di Ann Harrison [1.. Quindi, cercheremo di considerare il processo di creazione dell’immagine interna del database.
-
Il server legge 1024 byte dall’inizio del file e, se si tratta realmente di un file di database InterBase, definisce la dimensione della pagina di questo database e rilegge l’intera pagina di intestazione.
-
Dall’intestazione, il server della pagina estrae il numero della pagina puntatore che memorizza i riferimenti alle pagine dati, definendo la tabella RDB$Pages.
-
Il server si sposta su questa pagina puntatore e inizia a leggere le informazioni dalle pagine dati puntate. Riempie la prima tabella RDB$Pages con i dati. Questa tabella è una sorta di ponte tra gli oggetti fisici - le pagine dei file di database - e quelli logici - le tabelle. La struttura di RDB$Pages, come quella delle altre tabelle di sistema, è rigorosamente fissa in InterBase.
-
Dopo aver ricevuto i dati sull’allocazione delle pagine per le relazioni (le relazioni - in realtà, sono la stessa cosa delle tabelle ordinarie, e possiamo sostituire mentalmente questi concetti per semplificare), InterBase inizia a formare le strutture dati: prima le tabelle di sistema, i vincoli e gli indici, poi gli oggetti utente.
-
Dopo l’inizializzazione dei metadati di sistema e utente (tabelle, vincoli, indici e altri oggetti del database), InterBase restituisce all’utente, che ha chiesto di aprire un database, l’handle di questo database. Nell’handle principale c’è un identificatore che indica a InterBase con quale database lavorare, perché più utenti possono lavorare contemporaneamente e ciò significa che più database possono essere aperti.
-
Dopo queste operazioni, il database è considerato aperto e il server è pronto a eseguire le query dell’utente su di esso. Ora che è stato creato un ponte che collega la struttura fisica e logica del database, possiamo iniziare a studiare le peculiarità della struttura logica.
Struttura logica del database InterBase
La struttura logica è una nozione piuttosto vaga, quindi cercheremo di padroneggiare le idee chiave gradualmente, sperando che in seguito diventino intuitivamente chiare. La prima cosa che considereremo relativa alla struttura logica del database sono le tabelle di sistema e il loro contenuto. Le tabelle di sistema descrivono il sistema, così come i metadati utente. In generale, il termine «metadati» significa «dati che descrivono un insieme di dati». Il prefisso «meta» significa: «descrive un insieme». Ad esempio, il meta-linguaggio è un linguaggio che descrive un insieme di linguaggi. I metadati descrivono i dati utente, cioè tabelle, trigger, viste, procedure memorizzate e così via - tutto ciò che implementa le regole di memorizzazione ed elaborazione delle informazioni, per cui questo specifico database è stato creato.
È piuttosto curioso scoprire per la prima volta che tutti i metadati - tabelle utente, trigger, viste, così come tutti gli oggetti di sistema - sono memorizzati nelle stesse tabelle, dalle quali è possibile leggere e scrivere dati tramite normali query SQL. Queste tabelle differiscono «visivamente» solo per il fatto che i loro nomi iniziano con RDB$. Questi 4 simboli sono riservati per i nomi degli oggetti di sistema. Nessuna tabella utente, colonna o altro oggetto ha il diritto di avere nomi che iniziano con questi simboli. Formalmente si può creare una tabella il cui nome inizia con i simboli riservati, ma la documentazione InterBase sconsiglia di farlo.
Sorge una domanda: se i dati sulla struttura del database sono memorizzati nelle stesse tabelle dei dati utente, dove sono memorizzate le informazioni sulle tabelle che descrivono le tabelle? Un classico esempio del problema dell’«uovo e gallina» - come potrebbe uno apparire prima dell’altro, se sono interdipendenti? La risposta è che le tabelle di sistema nel loro stato primitivo sono fisse nei codici iniziali di InterBase e vengono aperte automaticamente quando si crea un database in un ordine definito. Abbiamo già parlato della tabella RDB$Pages che confronta le pagine fisiche nei file di database con oggetti definiti di questo database. La struttura di questa tabella è riportata di seguito:
Tabella 5. Tabella di sistema RDB$Pages
| Nome colonna | Tipo di dato | Descrizione |
| RDB$PAGE_NUMBER | INTEGER | Numero della pagina fisica |
| RDB$RELATION_ID | SMALLINT | Identificatore della tabella per cui la pagina è allocata |
| RDB$PAGE_SEQUENCE | INTEGER | Numero di questa pagina |
| RDB$PAGE_TYPE | SMALLINT | Tipo di pagina - vedere tabella 3 |
Ogni pagina dati è correlata a una determinata tabella. Questa relazione è supportata dal campo RDB$RELATION_ID, dove è memorizzato un riferimento alla tabella. Come descritto sopra, nel processo di costruzione dell’immagine interna del database, il server crea questa tabella e la riempie con i dati secondo un algoritmo prestabilito. Per essere precisi, al momento della costruzione dell’immagine interna del database, RDB$Pages non è una tabella, è solo un file dati di formato definito, noto a InterBase. Secondo un algoritmo fisso, il server legge i dati da questo file e crea una tabella - RDB$Relations - che è importante per l’intero database. Questa tabella descrive tutte le tabelle del database. Se eseguiamo una query SQL:
SELECT * from RDB$Relations
per scoprire a quali tabelle RDB$Relations contiene riferimenti, vedremo che contiene RDB$Pages e se stessa. È ovvio che in questo caso il server è un po’ furbo, sostituendo queste e altre tabelle di sistema in RDB$Relations con datazione retroattiva, legalizzandole in questo modo. Il server le registra come tabelle «normali», dove può aggiungere o eliminare record. In altre parole, fornisce un’interfaccia SQL standard per lavorare con i metadati.
E potrebbe sorgere una domanda abbastanza ragionevole: perché gli sviluppatori di InterBase dovrebbero adattare i loro dati di sistema in conformità con l’interfaccia utente? Vedete, i meccanismi interni di accesso e le operazioni di lettura sarebbero più rapidi. Naturalmente, c’è un grande senso nel fornire un meccanismo universale di lavoro con le tabelle che descrivono i metadati.
Il punto è che la struttura logica del database consiste non solo di tabelle ma anche di altri oggetti. In InterBase ci sono i seguenti oggetti:
-
Tabella
-
Vista
-
Trigger
-
Campo_calcolato
-
Validazione
-
Procedura
-
Indice_espressione
-
Eccezione
-
Utente
-
Campo
-
Indice
-
Funzione definita dall’utente (UDF)
Ancora non conosciamo con certezza la funzione di alcuni oggetti, ma sappiamo per certo che tutti devono essere descritti e memorizzati in una qualche forma conveniente per l’utente e per l’accesso dal kernel di InterBase. Sarebbe meglio salvare questi oggetti nelle tabelle di sistema. La loro aggiunta e modifica vengono eseguite tramite query SQL. Una soluzione intelligente, non è vero? L’implementazione del server è completamente separata da un database concreto - tutte le interconnessioni sono descritte da SQL e dalle sue estensioni - il linguaggio delle procedure memorizzate e dei trigger.
Quindi, tutti gli oggetti del server sono memorizzati in tabelle. Ogni tipo di oggetto ha la tabella che descrive tutte le istanze presenti nel database. Ad esempio, per i trigger esiste una tabella RDB$Triggers, per le procedure memorizzate - RDB$Procedures, le viste sono descritte nella tabella RDB$Relations.
Consideriamo in dettaglio la struttura dell’ultima tabella, che descrive tutte le tabelle e le viste in un database. La struttura della tabella RDB$RELATIONS è tratta da Language Reference per InterBase 6 ed è riportata di seguito nella tabella 6.
Tabella 6. Tabella di sistema RDB$Relations
| Nome colonna | Tipo di dato | Lunghezza | Descrizione |
| RDB$VIEW_BLR | BLOB | 80 | BLR: per le viste, contiene il BLR (Binary Language Representation) della query che InterBase esegue ogni volta che si accede alla vista. |
| RDB$VIEW_SOURCE | BLOB | 80 | Testo: per le viste, contiene il codice della query SQL che implementa questa vista. |
| RDB$_DESCRIPTION | BLOB | 80 | Descrizione utente della tabella o vista |
| RDB$RELATION_ID | SMALLINT | Contiene l’identificatore interno della tabella/vista | |
| RDB$SYSTEM_FLAG | SMALLINT | Definisce il tipo di tabella: dati utente - 0; informazioni di sistema > 0. | |
| RDB$DBKEY_LENGTH | SMALLINT | Lunghezza db$key | |
| RDB$FORMAT | SMALLINT | Riservato per uso interno di InterBase. Contiene il contatore di modifica dei metadati per la tabella data. | |
| RDB$FIELD_ID | SMALLINT | Il numero di campi nella tabella. | |
| RDB$RELATION_NAME | CHAR | 31 | Nome univoco della tabella. |
Nella descrizione di questa tabella di sistema, vediamo un’abbreviazione BLR. Per capire cosa sia, faremo un’escursione nel SQL. Come è noto, viste, trigger e procedure memorizzate sono codice scritto in un’estensione del linguaggio SQL (per ogni server DBMS ci sono le proprie estensioni). È vicino al linguaggio umano, il che permette di eseguire query facilmente. Ma InterBase, ovviamente, lo traduce in qualcosa di più «macchina» - precisamente in BLR (Binary Language Representation). Qualsiasi query, vista, trigger, procedura memorizzata viene sempre tradotta in BLR e poi trasmessa al kernel di InterBase per l’esecuzione.
BLR
BLR è un linguaggio speciale, usato come collegamento intermedio tra il codice SQL che un programmatore scrive e il codice macchina che il server accetta. Nessuno scrive direttamente in BLR - sarebbe piuttosto difficile perché per la massima velocità di esecuzione possibile in questo linguaggio viene usata la cosiddetta notazione polacca inversa. Ecco un piccolo esempio:
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,
Il BLR per le vostre query, procedure, trigger e altri trigger viene formato da un preprocessore speciale che fa parte del kernel del server. Come mostrato nella tabella 7, per le viste vengono memorizzati sia il testo (iniziale) della vista, sia la vista compilata, cioè il BLR. Quando si fa riferimento a qualsiasi oggetto che ha un BLR, il server esegue il codice binario dell’oggetto e non interpreta ogni volta il testo iniziale di questi oggetti, il che consente di accelerare l’esecuzione di query complesse.
Gerarchia degli oggetti in InterBase
Per avere un’idea chiara di cosa rappresentino gli oggetti del database, cercheremo di creare una gerarchia degli oggetti del database secondo il principio «chi contiene cosa». Le pagine fisiche dei file di database sono le prime che devono essere incluse nella nostra gerarchia come livello più basso dell’organizzazione dei dati. Poi le tabelle vanno come oggetti di base che descrivono tutti gli altri tipi di oggetti. Le tabelle descrivono procedure memorizzate, trigger, campi calcolati, validazioni, indici di espressione, eccezioni e così via. Attenzione - solo descrivono! Le tabelle contengono solo dichiarazioni e definizioni di questi oggetti, e gli oggetti sono implementati tramite BLR. Pertanto possiamo rappresentare le tabelle come una struttura portante che sostiene tutti gli altri oggetti del database. Il BLR sarà alla base della struttura come strato di implementazione, poi trigger, procedure memorizzate, indici di espressione e viste.
Per tranquillizzare gli specialisti della struttura interna di InterBase, che potrebbero obiettare che il BLR di molti oggetti (come le viste) è memorizzato nelle tabelle di sistema, commenteremo che questo atteggiamento è piuttosto difficile da esprimere in un’immagine e, per semplificazione, lo tralasciamo. Lo schema non ha l’obiettivo di ricreare in modo assolutamente accurato le interdipendenze degli oggetti del database; illustra solo la loro stretta interconnessione.
Il fatto che questi tipi di oggetti siano direttamente collegati al BLR, che li implementa senza alcuna logica intermedia, li unisce. Le eccezioni dovrebbero essere allocate separatamente - rappresentano tipi speciali di errori, definiti dall’utente. Le eccezioni vengono elaborate a livello del kernel di InterBase e quindi non hanno BLR. Tipi di vincoli come i check sono allocati sopra i trigger perché in realtà i trigger implementano la logica dei vincoli e dei check.
Una gerarchia degli oggetti della struttura logica e fisica del database è rappresentata nella figura 2.
Figura 10. Oggetti della struttura logica del database InterBase
Naturalmente, questo schema descrive la struttura logica e le interconnessioni degli oggetti nel database solo approssimativamente e ne dà un’idea generale. Chiunque voglia studiare la struttura dei metadati del database InterBase può eseguire il reengineering delle tabelle di sistema del database e considerare tutte le interconnessioni tra i suoi oggetti, oltre a fare riferimento alla documentazione e ai codici primari di InterBase. Questa tabella mostra solo gli oggetti principali del database. Descriviamo brevemente le funzioni principali che questi oggetti svolgono nel database.
Tabelle - l’oggetto principale, che contiene dati utente e di sistema. Una tabella ha un nome univoco e contiene un insieme di campi denominati. Un utente può inserire dati, estrarli e modificarli nelle tabelle. Possiamo dire che una tabella è simile alle normali tabelle di carta disegnate a mano.
Trigger - parti eseguibili del codice, utilizzate per implementare azioni aggiuntive al momento delle operazioni sui dati. I trigger vengono eseguiti prima o dopo le operazioni di inserimento, modifica o eliminazione e consentono di realizzare la sostituzione di valori nei record appena creati e molte altre cose.
Una stored procedure è un potente strumento per implementare la logica di business a livello di database. Essendo eseguita a livello del server, funziona molto rapidamente e consente di eseguire una serie di operazioni su insiemi di dati. Le stored procedure di InterBase restituiscono insiemi di dati SQL standard, sui quali sono possibili tutte le operazioni SQL, inclusa l’unificazione con altre tabelle.
Le viste sono query SQL compilate, eseguite sul server. Le viste consentono di organizzare insiemi di dati, trasferendo una parte della logica di business sul server.
Validazioni sono vincoli impostati sui valori dei campi nella tabella. Ad esempio, possiamo indicare che il campo dato accetterà solo valori positivi. I vincoli sui valori dei campi sono implementati dai trigger e consentono di controllare efficacemente l’integrità referenziale a livello di database. Di solito i vincoli vengono utilizzati per impedire che nella tabella vengano inseriti valori errati.
Utenti - InterBase ci consente di avere più utenti per lavorare con il database e distribuire tra loro i diritti di accesso ai diversi oggetti del database. In questo modo, possiamo controllare le autorizzazioni per queste o quelle operazioni sul database.
Funzioni definite dall’utente (UDF) - funzioni, definite dall’utente. È una delle capacità più potenti di InterBase, che ci consente di estendere l’interfaccia SQL standard con le proprie funzioni. Ad esempio, funzioni di lavoro con le stringhe come UPPER (impostazione di tutti i simboli nel registro superiore), sono implementate nella libreria UDF standard, inclusa nel set di InterBase. Grazie alla possibilità di creare proprie UDF, gli sviluppatori possono estendere la funzionalità di InterBase praticamente con qualsiasi funzione. Possiamo utilizzare qualsiasi ambiente di programmazione che consenta di creare librerie dinamiche (Visual C++, C++ Builder, Delphi ecc.) per creare UDF.
Conclusione
In questo capitolo abbiamo considerato per la prima volta le questioni relative all’implementazione dell’archiviazione e dell’elaborazione dei dati all’interno del database InterBase. Purtroppo, non possiamo fare una breve rassegna di questo tema senza ricorrere a un gran numero di termini e analogie imprecise. Se descrivessimo la struttura fisica e logica del database in modo più dettagliato, dovremmo comunque fare riferimento ai codici primari di InterBase, ma sarebbe un altro libro.
Tuttavia, pensiamo che sarebbe utile per ogni programmatore familiarizzare con il contenuto del prodotto che usa ogni giorno.
Bibliografia
-
«The On-Disk Structure of InterBase» di Ann.W.Harrison
-
«Space Management in InterBase» di Ann W.Harrison
-
«Structure of a Data Page» di Paul Beach (Con ringraziamenti a Dave Schnepper e Deej Bredenberg)