Database fysieke structuur (InterBase en Firebird)
Alexey Kovyazin, Sergey Vostrikov, laatste update 05-Juni-2004
Fysieke structuur van de database
Waarom moeten we de fysieke structuur van een InterBase-database bestuderen?
Wanneer we spreken over de fysieke structuur van een InterBase-database, bedoelen we meestal dat het gegevens vertegenwoordigt vanuit het oogpunt van low-level gegevensorganisatie - tot op het niveau van bytes. Veel programmeurs die applicaties ontwikkelen met behulp van een hogere programmeertaal verwaarlozen het bestuderen van low-level details. Het kennen van de belangrijkste principes van gegevensorganisatie binnen de database is echter de sleutel tot een effectief ontwerp van databaseapplicaties. Daarom zullen we een excursie maken naar de binnenkant van de InterBase-databaseorganisatie en ontdekken hoe deze is ingericht.
Waar dient een databasebeheersysteem (DBMS) eigenlijk voor? Uiteraard voor het opslaan en beheren van gegevens. Het klinkt banaal, maar het is de moeite waard om erover na te denken. Een gebruiker plaatst gegevens in het DBMS, dat deze gegevens op de een of andere manier vertaalt naar voor hem begrijpelijke interne formaten. Je kunt je “0 en 1” voorstellen als de woorden “intern gegevensformaat” enige moeite met associaties veroorzaken. Het DBMS slaat deze gegevens op en moet ze op het moment van de eerste aanvraag uit zijn formaat extraheren, omzetten naar een geschikte weergave en aan een gebruiker geven.
Het onderwerp van dit hoofdstuk is hoe het DBMS zijn gegevens opslaat, in welke vorm en hoe ze op het laagste niveau zijn georganiseerd. We zullen proberen je uit te leggen hoe we uit bits en bytes, die op de harde schijf liggen, waardevolle gegevens krijgen.
InterBase-databasebestanden
Wanneer we over een database spreken, bedoelen we meestal het DBMS zelf en gebruikersinformatie, en zelfs de programma’s van clients die met gegevens werken. In dit hoofdstuk beschouwen we een database als databasebestanden.
Een InterBase-database vertegenwoordigt een of meerdere bestanden die informatie bevatten over alles wat met deze database verbonden is. Informatie over gebruikers is een uitzondering, omdat gebruikers op het niveau van de hele server worden gedefinieerd en afzonderlijk worden opgeslagen, in de beveiligingsdatabase admin.ib (dit was ISC4.GDB in versies vóór 7).
Advies: Bekijk het hoofdstuk “Server- en databasebeveiliging” om meer te leren over de beveiligingsprincipes van InterBase.
Alle informatie over de database wordt dus binnen deze bestanden opgeslagen: de gegevens zelf, indexen, triggers, opgeslagen procedures, enz.
Een InterBase-database voor een gemiddeld project vertegenwoordigt één bestand, omdat moderne InterBase-versies 64bitIO kunnen gebruiken om met het gegevensbestand te werken en dit geeft je de mogelijkheid om een gegevensbestand tot 64Gb te hebben. Eerdere versies van InterBase hadden de beperking van 4 gigabytes per databasebestand (tot 64 Tbytes voor de hele database). Zoals we kunnen aannemen, zijn 64 gigabytes ruim voldoende om informatie van bijna elke databaseapplicatie op te slaan. Maar indien nodig kunnen we een database in meerdere bestanden verdelen. Er zijn trouwens InterBase-databases van honderden gigabytes groot.
IBSurgeon - een gids door InterBase-database
We moeten de structuur van InterBase-databasebestanden in detail kennen. Daarom is het wenselijk om een handig hulpmiddel te hebben dat direct met databasebestanden werkt, niet via de InterBase-serverkernel. De eenvoudigste manier is om een gewone hexadecimale viewer te gebruiken en te proberen de structuur van databasebestanden te begrijpen aan de hand van de HEX-weergave. Dat zou behoorlijk vermoeiend werk zijn.
Maar gelukkig is er een hulpmiddel voor direct werk met InterBase-databases. Het is een IBSurgeon Editor - een hulpmiddel voor direct low-level werk met InterBase-databases, dat kan worden gebruikt voor het bestuderen van de interne structuur van InterBase-databases en het diagnosticeren van beschadigde databases om ze te herstellen. Zie voor meer details de bijlage “Hulpmiddelen voor beheerders en InterBase-ontwikkelaars”.
IBSurgeon gebruikt zijn eigen alternatieve mechanisme voor databasetoegang dat het mogelijk maakt databases in elke staat te openen en te bekijken, inclusief zwaar beschadigde databases die niet kunnen worden geopend door de InterBase/FireBird/Yaffil-serverkernel.
We zullen IBSurgeon gebruiken om de interne structuur van de database te illustreren.
Bestanden *.IB/*.FDB van binnenuit
IB is een extensie die wordt aanbevolen voor InterBase-databasebestanden, en FDB voor Firebird (voorheen was het GDB). Het eerste wat we moeten zeggen over de structuur van een IB-bestand is dat het een reeks pagina’s van strikt gedefinieerde grootte vertegenwoordigt. De grootte van het databasebestand is deelbaar door een paginagrootte, die ongewijzigd blijft voor alle bestanden van deze database. Verschillende InterBase-versies ondersteunen verschillende paginagroottes, zoals weergegeven in tabel 1. De paginagrootte wordt ingesteld bij het maken van een database en kan niet worden gewijzigd tijdens de levenscyclus ervan. Met andere woorden, we kunnen een paginagrootte alleen wijzigen bij het herstellen van een database vanuit een back-up.
Tabel 1. Paginagrootte ondersteund door verschillende InterBase-versies
| InterBase-versie | Paginagrootte, bytes | ||||
| 1024 | 2048 | 4096 | 8192 | 16384 | |
| InterBase 4.0 | * | * | * | * | |
| InterBase 5.x | * | * | * | * | |
| InterBase 6.x-7.x | * | * | * | * |
Het lezen en schrijven van gegevens in een database wordt pagina voor pagina uitgevoerd; veel belangrijke server- en databasekenmerken, zoals de grootte van de databasecache, zijn afhankelijk van een paginagrootte en worden geteld in “pagina’s”.
Laten we een InterBase-database openen met IBSurgeon. Het is voldoende om dubbel te klikken op het databasebestand. Afbeelding 1 toont een lijst van pagina’s die verschijnt nadat IBSurgeon de database heeft geopend:

Afbeelding 1. Een lijst van databasepagina’s
Pagina’s kunnen van verschillende typen zijn, elk met een bepaald doel. De onderlinge afhankelijkheden van verschillende typen worden voorwaardelijk weergegeven in afbeelding 2. Afbeelding 2 toont schematisch een toewijzing van pagina’s in het databasebestand - van links naar rechts, van boven naar beneden, gerekend vanaf het begin van het bestand. Pagina’s van hetzelfde type komen niet strikt na elkaar - ze kunnen gemakkelijk worden gemengd, toegewezen in een bestand in de volgorde waarin ze door een server zijn gemaakt bij het uitbreiden of maken van databases.

Afbeelding 2. Onderlinge afhankelijkheden tussen verschillende typen pagina’s in een InterBase-database
Je moet hebben opgemerkt dat sommige typen pagina’s geen verwijzingen naar andere typen pagina’s hebben. Er is hier echter geen tegenstrijdigheid; het punt is dat deze typen pagina’s zijn gekoppeld en gebruikt op een ander structureel niveau. Ze kunnen worden gekoppeld aan de RDB$PAGES-tabel en andere systeemtabellen (deze tabel en andere systeemobjecten zullen we hieronder bekijken - in het hoofdstuk “Logische structuur van de database”). In afbeelding 2 zien we alleen expliciete verwijzingen tussen de pagina’s op fysiek niveau.
Laten we in detail bekijken welke typen pagina’s er in een InterBase-database zijn. In het bestand ods.h uit de set van InterBase-broncodes staat informatie over alle mogelijke typen pagina’s. We zullen vaak naar dit bestand verwijzen om gegevens te ontvangen, niet alleen over ODS maar ook over veel andere fundamentele zaken van de InterBase-kernel in de oorspronkelijke bron. In totaal worden 11 typen pagina’s gedeclareerd, maar slechts 9 ervan zijn de moeite waard om uit te leggen (we kunnen dit duidelijk zien in tabel 2). Paginatypen met identificatoren 0 en 1 zijn ongedefinieerd of niet gebruikt.
Tabel 3. Paginatypen in FB
| De definitie in ods.h | Paginatype-identificator | Paginabeschrijvingen |
| pag_undefined | 0 | Ongedefinieerd - Als een pagina dit paginatype heeft, is deze waarschijnlijk vrij |
| pag_header | 1 | Databasekoptekstpagina |
| pag_pages | 2 | Paginainventarispagina (of Space inventory page - SIP) |
| pag_transactions | 3 | Transactie-inventarispagina (TIP) |
| pag_pointer | 4 | Aanwijzerpagina |
| pag_data | 5 | Gegevenspagina |
| pag_root | 6 | Indexwortelpagina |
| pag_index | 7 | Index (B-boom) pagina |
| pag_blob | 8 | Blob-gegevenspagina |
| pag_ids | 9 | Gen-ids |
| pag_log | 10 | Write ahead log-informatie |
Elke pagina heeft een koptekst met informatie over een paginatype en een nummer van de volgende pagina van hetzelfde type. We kunnen de volledige lijst van parameters die elke paginakoptekst bevat verkrijgen als we de pag-structuur in het definitiebestand ods.h bekijken.
/\* Basis paginakoptekst */
typedef struct pag {
SCHAR pag_type; /*paginatype-identificator*/
SCHAR pag_flags; /*pagina-vlaggen*/
USHORT pag_checksum; /*pagina-checksum: deze is gelijk aan 12345 na versie 5.0 */
ULONG pag_generation; /*paginageneratie */
ULONG pag_seqno; /* WAL-seqno van laatste update - verouderd*/
ULONG pag_offset; /* WAL-offset van laatste update - verouderd*/
} *PAG;
Paginatypen en hun gebruik
Laten we elk paginatype in detail bekijken en kennis maken met hun functie en de informatie die ze bevatten. We beginnen stap voor stap - vanaf de eerste pagina.
Elke bewerking met een database begint met het lezen van de databasekoptekstpagina (of koptekstpagina). De databasekoptekstpagina staat als eerste in alle databasebestanden. Dienovereenkomstig wordt deze als eerste weergegeven in afbeelding 2 (als we ons voorstellen dat de afbeelding een uitbreiding van het databasebestand van links naar rechts, van boven naar beneden vertegenwoordigt).
Een koptekstpagina bevat informatie over de database als geheel. In afbeelding 3 wordt een gegevenspagina weergegeven zoals IBSurgeon deze aan ons toont:

Afbeelding 3. Databasekoptekstpagina.
Je kunt een idee krijgen van de inhoud van de koptekstpagina door databasestatistieken te ontvangen. Hiervoor kun je het opdrachtregelhulpprogramma gstat gebruiken of een ander handiger hulpmiddel voor InterBase-beheer uit de lijst in de bijlage “Hulpmiddelen voor beheerders en InterBase-ontwikkelaars”. Zie voor meer details over het proces van het ontvangen van statistieken en de beschrijving van de koptekstpagina het hoofdstuk “Statistieken”.
Het moet worden opgemerkt dat de koptekstpagina belangrijke informatie bevat zoals paginagrootte, ODS-versienummer (informatie hierover vind je hieronder), gegevens van databasecreatie, informatie over transacties en een reeks verschillende informatie. De Implementation ID slaat bijvoorbeeld informatie op over onder welk besturingssysteem deze database is gemaakt.
Bij het verbinden met een database leest de InterBase-server de eerste 1024 bytes aan informatie vanaf het begin van het bestand en bepaalt op basis van de gelezen waarden of het bestand dat in de verbindingsregel wordt aangegeven een InterBase-database is of niet. Vervolgens leest de server het ODS-versienummer van een koptekstpagina en de paginagrootte in deze database en, als de ODS-versie compatibel is met de serverimplementatie, herleest hij de hele koptekstpagina met de juiste paginagrootte, ontvangen uit de eerste 1024 bytes. Daarna worden de resterende belangrijke databaseparameters zoals lees-schrijfmodus, database-dialect enz. van de koptekstpagina gelezen.
Op de koptekstpagina staat een verwijzing naar de eerste van de aanwijzerpagina’s, die verwijzingen naar gegevenspagina’s bevatten die metadata bevatten: de RDB$Pages-tabel (zie hieronder in het hoofdstuk “Logische structuur van de InterBase-database”). In afbeelding 2 wordt deze verwijzing geïllustreerd door een pijl met het opschrift “Nummer van de 1e aanwijzerpagina in de database”. De server leest het nummer van de 1e aanwijzerpagina van de koptekstpagina en gaat er naartoe. Een aanwijzerpagina bestaat uit een geordende reeks gegevenspaginanummers die een bepaalde tabel vormen (een tabel wordt beschouwd als een SQL-object, beschreven door de logische structuur van de database). Nu kun je zien hoe IBSurgeon de aanwijzerpagina interpreteert (bekijk afbeelding 4):

Afbeelding 4. Aanwijzerpagina van een InterBase-database
De pagina bevat een gegevenspagina-vector; deze gegevens vormen een bepaalde tabel in een database. Deze vector vertegenwoordigt een reeks aanwijzers die overeenkomen met de nummers van gegevenspagina’s in het bestand. De server leest een 4-byte nummer van de gegevenspagina en gaat naar een benodigde gegevenspagina. Wanneer hij naar de 1e gegevenspagina van RDB$Pages gaat, begint de server met het opbouwen van de interne databaserepresentatie die later door de server wordt gebruikt voor alle bewerkingen met de database. RDB$Pages slaat niet alleen verwijzingen op naar gegevenspagina’s die informatie over de database bevatten, maar ook naar de resterende pagina’s die een rol spelen bij het waarborgen van de databasewerking.
We noemen deze tabel vaak, die strikt genomen betrekking heeft op de logische structuur van de database. Toch is alles met elkaar verbonden, daarom kunnen we iets niet beschrijven zonder naar iets anders te verwijzen.
Een van de belangrijke paginatypen is de transactie-inventarispagina (TIP). Deze pagina’s bestaan, net als alle pagina’s, uit een header en een hoofdgedeelte dat een reeks van 2-byte-sequenties vertegenwoordigt. De sequenties beschrijven de staat van transacties in een database (voor meer details over transacties, zie hoofdstuk «Transacties»).
Tabel 4. Mogelijke transactiestaten in TIP
| Waarde van sequentie op PIP | Betekenis |
| 0 | Transactie is niet gestart, actief of verloren zonder commit of rollback |
| 1 | Transactie heeft Commit uitgevoerd |
| 2 | Transactie heeft Rollback uitgevoerd |
| 3 | Limbo-transactie (voor 2PC) |
Elke recordversie heeft zijn transactie-identificatie, waardoor gelijktijdig uitgevoerde transacties elkaars staat kunnen «leren kennen» en conflicten kunnen oplossen tijdens multi-user werk (zie hoofdstuk “Multi-generation architectuur van InterBase” voor meer informatie over recordversies en andere zaken).
Database-headerpagina, pointerpagina’s en TIP behoren tot de «housekeeping»-paginatypen, die alleen door de server worden gebruikt. InterBase-gebruikers krijgen nooit expliciet de informatie die ze bevatten. Pagina’s die informatie opslaan over de toewijzing van pagina’s (meestal aangeduid als Page Inventory Pages (PIP) of Space Inventory Pages (SIP)) behoren ook tot het housekeeping-paginatype. Deze pagina’s bevinden zich vanaf de tweede, dat wil zeggen dat de eerste PIP direct na een headerpagina komt, en verschijnen in een database met vaste paginaintervallen van andere typen. De grootte van deze intervallen bepaalt op hoeveel pagina’s van andere typen PIP verschijnt en hangt af van de paginagrootte die voor deze database is ingesteld. Page Inventory Pages worden niet meegeteld op Pointer-pagina’s en worden niet vermeld in RDB$Pages. De integriteit van deze pagina’s is van vitaal belang voor een succesvolle werking van de hele database, omdat de PIP-inhoud de staat van alle overige pagina’s in de database beschrijft. Elke databasepagina kan 3 staten hebben: niet toegewezen, toegewezen met ruimte, toegewezen en vol. Wanneer er behoefte is aan extra ruimte voor nieuwe gegevens, controleert de server de PIP om niet toegewezen pagina’s te vinden. Als zo’n pagina bestaat, wijzigt de server de staat ervan in toegewezen met ruimte. Als er geen niet toegewezen pagina’s zijn, breidt de database uit - er wordt een nieuwe gegevenspagina toegevoegd.
Een voorbeeld van de gegevenspagina in IBSurgeon en de gegevens die deze bevat, wordt gegeven in afbeelding 5.

Afbeelding 5. Page Inventory Page
Zodra de pagina is toegewezen, schrijft InterBase de staat ervan op SIP en schrijft vervolgens de pagina zelf. Daarna moeten we deze opnieuw gevormde pagina toevoegen aan een groot aantal pagina’s, bijvoorbeeld aan gegevenspagina’s voor een tabel. Hiervoor moeten we de verwijzing naar deze nieuwe pagina schrijven op de laatste pagina van dit grote aantal pagina’s - bijvoorbeeld op de laatste gegevenspagina van een tabel. Als de server zijn werk onderbreekt direct na het schrijven op SIP, maar zonder de verwijzing op pagina’s te schrijven die naar de zojuist toegewezen pagina verwijzen, dan wordt deze pagina een wees. Een weespagina is fysiek aangemaakt, gereserveerd op SIP, maar er zijn geen verwijzingen naar vanuit andere pagina’s, wat betekent dat de server deze niet zal kunnen vinden en gegevens op schijf zal schrijven. De weespagina is gemarkeerd met een rood vierkant op afbeelding 2. Weespagina’s ontstaan meestal als gevolg van een onverwachte stroomuitval van de server en worden «gecureerd» door het speciale hulpmiddel voor databasereparatie gfix (of door FirstAID) (of door IBSurFirstAID).
Voordat we gegevenspagina’s bekijken, moeten we belangrijke paginatypen noemen: generator- en indexpagina’s. Generatorpagina’s vertegenwoordigen een reeks van 4-byte-getallen die de generatorstaten weergeven. Eigenlijk is de generator een gewone teller.
Op afbeelding 6 zie je een generatorpagina. Let op dat hoewel IBSurgeon generatornamen toont, het niet zo is dat deze namen op generatorpagina’s worden opgeslagen. Dit is gedaan voor het gemak van de gebruiker die de database bestudeert. In werkelijkheid worden generatornamen opgeslagen in de systeemtabel RDB$Generators.

Afbeelding 6. Generatorpagina (gen-ids)
Zoals je in dit voorbeeld ziet, bevat de database systeemgenerators, beginnend met het RDB$-voorvoegsel, en door de gebruiker gedefinieerde generators. Als je meer wilt weten over de functie en het gebruik van generators bij het ontwikkelen van InterBase-databasetoepassingen, zie hoofdstuk «Tabellen. Primaire sleutels en generators». Generatorpagina’s worden samen met andere pagina’s in de RDB$Pages-tabel meegeteld.
Elke tabel heeft ten minste één index-rootpagina, ongeacht of deze indexen heeft of niet. Deze pagina bevat verwijzingen naar indexpagina’s voor een specifieke tabel. We kunnen zeggen dat de index-rootpagina van hetzelfde belang is voor indexpagina’s als de pointerpagina voor gegevenspagina’s. Daarom geeft IBSurgeon deze op een vergelijkbare manier weer. Een voorbeeld van een index-rootpagina wordt gegeven in afbeelding 7.

Afbeelding 7. Index-rootpagina
De index-rootpagina bevat een lijst van pagina’s waar indexwaarden zijn opgeslagen, evenals indexinformatie - de selectiviteit van de index en verschillende vlaggen. Voor meer details over indexen, hun rol en gebruik in InterBase-databases, zie hoofdstuk «Indexen».
Indexpagina’s bevatten direct de waarden van indexen, of als indexniveau >0, verwijzingen naar de onderliggende indexpagina’s. Hier is een voorbeeld van een indexpagina (afbeelding 8).

Afbeelding 8. Index- (B-tree) pagina
De indexpagina slaat verpakte waarden van geïndexeerde gegevens op. Er wordt een vrij ingewikkeld indexeringsmechanisme gebruikt, vooral bij het maken van samengestelde indexen (met meerdere velden).
In het algemeen slaan gegevenspagina’s en pagina’s met BLOB-waarden gebruikersinformatie op. Gegevenspagina’s bevatten records in gebruikers tabellen van de database, fragmenten van records, oude versies, verschillen tussen versies, BLOB-velden enzovoort. Wat BLOB-velden betreft, deze zijn verbonden met records op gegevenspagina’s en bevatten gegevens van grote omvang die niet op de gegevenspagina kunnen worden geplaatst. De referentiële manier van het opslaan van BLOB-waarden maakt het mogelijk grote gegevens op te slaan.
Een voorbeeld van de presentatie van een gegevenspagina in IBSurgeon wordt gegeven in afbeelding 9:

Afbeelding 9. Gegevenspagina
De header van de gegevenspagina bevat het paginatype en de identificatie van de eigenaartabel (relationID). Records worden op gegevenspagina’s opgeslagen vanaf het einde van de pagina en worden dichter naar het begin van de pagina toegewezen naarmate ze worden gevuld.
We kunnen dit bevestigen als we naar rij-indexen kijken, die 2 waarden bevatten - offset op de pagina en de lengte ervan. Zoals je aan het begin van de rij ziet, zijn er records toegewezen aan het einde van de pagina - bijvoorbeeld, het eerste record heeft een offset van 8156 bytes en een lengte van 34 bytes - dus het eindigt op 8156+34=8192 bytes - op de rand van de pagina (in ons geval is de paginagrootte 8192 bytes). Wanneer de pagina vol is (met gegevens van bovenaf en rij-indexen van onderaf), begint de server nieuwe records en versies van oude records naar nieuwe pagina’s te schrijven. Uit het hierboven beschreven mechanisme van paginavulling kunnen we gemakkelijk afleiden waarom InterBase-specialisten nadrukkelijk aanbevelen om gegevenspagina’s van grote omvang te gebruiken (minimaal 4096 bytes, beter 8192). Als we een tabel maken waarvan één record vrij groot zal zijn (bijvoorbeeld 10 velden van VARCHAR (255)), dan zullen deze meer dan 2550 bytes in beslag nemen. Dat betekent dat zo’n record te groot zal zijn voor een pagina van kleine omvang (1024 of 2048). Het is duidelijk dat de noodzaak om meerdere pagina’s van schijf te laden om één enkel record te lezen, het werk met je database niet zal versnellen. Daarom wordt aanbevolen om de grootte van de gegevenspagina opnieuw te definiëren bij het maken of herstellen van een database, omdat de grootte van 1024 bytes standaard is ingesteld. We hebben zojuist kort de belangrijkste typen InterBase-databestandpagina’s en hun functie bekeken. Nu kunnen we overgaan naar een hoger structureel niveau.
ODS
ODS is een afkorting voor On-Disk Structure, dat wil zeggen de gegevensstructuur van een InterBase-database op schijf. ODS definieert hoe de gegevens in de databasebestanden zijn georganiseerd. De definitie van de belangrijkste constanten en gegevensstructuren voor het implementeren van de On-Disk-structuur staat in het bestand ods.h uit de set InterBase-broncodes. ODS is veranderd tijdens het ontwikkelingsproces van InterBase, en bij het werken met een concrete database ontdekt de server het ODS-versienummer om te weten waar hij mee te maken heeft. Het bestand ods.h toont ons de volgende versies van de On-Disk-structuur:
-
ODS 5 werd gebruikt door InterBase 3.3 en wordt niet ondersteund door hogere versies
-
ODS 6 en ODS 7 zijn nooit uitgekomen
-
ODS 8 wordt gebruikt door InterBase 4.0
-
ODS 9 wordt gebruikt door InterBase 4.5 en hoger
-
ODS 10 kwam uit met InterBase 6
-
ODS 11 kwam uit met InterBase 7.0
Naast de grote ODS-versies zijn er kleine versies die afhangen van een concrete versie van de databaseserver die ze heeft gemaakt. Grote versienummers worden geschreven in het gehele deel van het nummer, dat de versie aangeeft, kleine - in het fractionele deel. Bijvoorbeeld, serverversie 4.0 maakt databases met ODS 8.0 en InterBase 4.2 - 8.2. De overgang tussen kleine versies van onder naar boven wordt automatisch uitgevoerd. Het is bijvoorbeeld voldoende om een database met ODS 8.0, gemaakt door server 4.0, te openen met InterBase 5.6, en de ODS van deze database zal versie 8.2 hebben. De overgang tussen grote databaseversies wordt alleen uitgevoerd via databaseback-up, met behulp van een oude versie, en herstel, met behulp van een nieuwe serverversie. Het proces van overgang tussen versies wordt in detail beschreven in hoofdstuk 1.4 «Migratie».
Het belangrijke punt bij de implementatie van ODS-ondersteuning voor InterBase-versies 4.x en 5.x is de achterwaartse compatibiliteit van InterBase-servers 4.x en 5.x met een versie die één eenheid lager is dan de implementatie van een concrete server. InterBase ondersteunt verschillende mogelijke ODS, en kiest op basis van de ODS-versie bij het verbinden met een concrete database de ondersteuning van de vereiste ODS-implementatie. Het mechanisme om te beslissen welke ODS-ondersteuningsimplementatie in een concreet geval te kiezen, wordt Y-Valve genoemd ((c) van Steve Trenton).
Simpel gezegd, een database met ODS 8.x, overeenkomend met InterBase 4.0, kan worden geopend in InterBase 5.x.
De volledige ODS-compatibiliteitstabel wordt hieronder weergegeven:
| InterBase-versie | Grote ODS | Kleine ODS |
| 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 |
ODS heeft neerwaartse compatibiliteit. Met andere woorden, een server met een hogere versie en al zijn hulpmiddelen zal kunnen werken met een database die door een server van een eerdere versie is gemaakt, maar niet andersom. Als je probeert een database te openen die is gemaakt in InterBase versie 6 met InterBase 5.x, ontvang je een foutmelding «Unsupported On-disk structure: Found ODS 10, supported ODS 9».
Beschrijving van de overgang tussen versies van onder naar boven en omgekeerd, zie hoofdstuk «Migratie».
ODS is erg belangrijk voor kwesties met betrekking tot back-up en database-extractie, evenals het herstel van beschadigde databases. Hulpmiddelen voor back-up gbak en herstel gfix controleren de ODS-versie en zullen gewoon niet werken als de ODS-versie van de database waarvoor ze moeten dienen groter is dan de versie die in hen is geïmplementeerd. Dit betekent dat gbak van 4.x geen databaseback-up kan maken als deze is gemaakt door server 5.x, hoewel het omgekeerde gemakkelijk is.
Een brug tussen de fysieke en logische structuur van de database
We hebben de fysieke structuur van databasebestanden in het algemeen bekeken. Nu moeten we overgaan naar de logische structuur van de database. Laten we een brug slaan tussen de fysieke en logische niveaus van informatieweergave in een database, zodat er geen scheiding is in de begrippen en geen hiaten in het materiaal. Alles wat op verschillende databasepagina’s is opgeslagen, moet op de een of andere manier in het computergeheugen worden georganiseerd; gegevens uit het databasebestand moeten worden omgezet in een set intraserverobjecten en -variabelen. Deze set wordt een intern databasebeeld genoemd volgens de terminologie van Ann Harrison [1.. Dus we zullen proberen het proces van het maken van een intern databasebeeld te bekijken.
-
De server leest 1024 bytes vanaf het begin van het bestand en als het echt een InterBase-databasebestand is, bepaalt hij de paginagrootte van deze database en herleest hij de volledige headerpagina.
-
Uit de header haalt de paginaserver het nummer van de pointerpagina die verwijzingen naar datapagina’s opslaat, waarmee de RDB$Pages-tabel wordt gedefinieerd.
-
De server gaat naar deze pointerpagina en begint informatie te lezen van de aangewezen datapagina’s. Hij vult de eerste RDB$Pages-tabel met gegevens. Deze tabel is een soort brug tussen fysieke objecten - pagina’s van databasebestanden - en logische objecten - tabellen. De structuur van RDB$Pages is, net als die van andere systeemtabellen, strikt vastgelegd in InterBase.
-
Nadat hij gegevens heeft ontvangen over de paginatoewijzing aan relaties (relaties - in feite hetzelfde als gewone tabellen, en we kunnen deze begrippen voor de eenvoud mentaal vervangen), begint InterBase met het vormen van datastructuren: eerst systeemtabellen, constraints en indexen, daarna gebruikersobjecten.
-
Na de initialisatie van systeem- en gebruikersmetadata (tabellen, constraints, indexen en andere databaseobjecten), geeft InterBase de handle van deze database terug aan de gebruiker die vroeg om een database te openen. In de handle zit een identificatie die InterBase laat zien met welke database het moet werken, omdat meerdere gebruikers tegelijk kunnen werken en dat betekent dat meerdere databases geopend kunnen zijn.
-
Na deze bewerkingen wordt de database als geopend beschouwd en is de server klaar om gebruikersquery’s erop uit te voeren. Nu er een bepaalde brug is geslagen die de fysieke en logische structuur van de database verbindt, kunnen we beginnen met het bestuderen van de eigenaardigheden van de logische structuur.
Logische structuur van InterBase-database
De logische structuur is een nogal vaag begrip, daarom zullen we proberen de kernideeën geleidelijk onder de knie te krijgen, in de hoop dat ze later intuïtief duidelijk worden. Het eerste wat we zullen bekijken met betrekking tot de logische structuur van de database zijn systeemtabellen en hun inhoud. Systeemtabellen beschrijven het systeem, evenals gebruikersmetadata. In het algemeen betekent de term «metadata» «gegevens die een set gegevens beschrijven». Het voorvoegsel «meta» betekent: «beschrijft een set». Een metataal is bijvoorbeeld een taal die een set talen beschrijft. Metadata beschrijven gebruikersgegevens, dat wil zeggen tabellen, triggers, views, opgeslagen procedures enzovoort - alles wat de regels implementeert voor het opslaan en verwerken van de informatie, waarvoor deze concrete database is gemaakt.
Het is nogal grappig om bij de eerste kennismaking te ontdekken dat alle metadata - gebruikerstabellen, triggers, views, evenals alle systeemobjecten - zijn opgeslagen in dezelfde tabellen, waaruit je gegevens kunt lezen en schrijven met gewone SQL-query’s. Deze tabellen verschillen «visueel» alleen doordat hun namen beginnen met RDB$. Deze 4 symbolen zijn gereserveerd voor de namen van systeemobjecten. Geen enkele gebruikerstabel, kolom of ander object heeft het recht om namen te hebben die met deze symbolen beginnen. Formeel kun je een tabel maken waarvan de naam met de gereserveerde symbolen begint, maar de InterBase-documentatie raadt dit af.
Er rijst een vraag: als de gegevens over de databasestructuur zijn opgeslagen in dezelfde tabellen als de gebruikersgegevens, waar wordt dan de informatie over de tabellen die tabellen beschrijven opgeslagen? Een klassiek voorbeeld van het probleem van «kip en ei» - hoe kan het ene eerder verschijnen dan het andere, als ze onderling afhankelijk zijn? Het antwoord is dat systeemtabellen in hun primitieve staat zijn vastgelegd in de initiële InterBase-code en automatisch worden geopend bij het creëren van een database in een vaste volgorde. We hebben al gesproken over de RDB$Pages-tabel die fysieke pagina’s in databasebestanden vergelijkt met bepaalde objecten van deze database. De structuur van deze tabel wordt hieronder gegeven:
Tabel 5. Systeemtabel RDB$Pages
| Kolomnaam | Datatype | Beschrijving |
| RDB$PAGE_NUMBER | INTEGER | Nummer van fysieke pagina |
| RDB$RELATION_ID | SMALLINT | Identificatie van tabel waarvoor de pagina is toegewezen |
| RDB$PAGE_SEQUENCE | INTEGER | Nummer van deze pagina |
| RDB$PAGE_TYPE | SMALLINT | Paginatype - zie tabel 3 |
Elke datapagina is gerelateerd aan een bepaalde tabel. Deze relatie wordt ondersteund door het veld RDB$RELATION_ID, waar een verwijzing naar de tabel wordt opgeslagen. Zoals hierboven beschreven, maakt de server tijdens het construeren van het interne databasebeeld deze tabel en vult deze met gegevens volgens een vast algoritme. Om precies te zijn: op het moment van het construeren van het interne databasebeeld is RDB$Pages geen tabel, maar slechts een gegevensbestand van een bepaald formaat, bekend bij InterBase. Volgens een vast algoritme leest de server gegevens uit dit bestand en maakt een tabel - RDB$Relations - die belangrijk is voor de hele database. Deze tabel beschrijft alle databasetabellen. Als we een SQL-query uitvoeren:
SELECT * from RDB$Relations
om te ontdekken naar welke tabellen RDB$Relations verwijzingen bevat, zullen we zien dat het RDB$Pages en zichzelf bevat. Het is duidelijk dat de server in dit geval een beetje sluw is, door deze en andere systeemtabellen in RDB$Relations met terugwerkende kracht in te voegen en ze op die manier te legaliseren. De server registreert ze als «normale» tabellen, waar hij records kan toevoegen of verwijderen. Met andere woorden, hij biedt een standaard SQL-interface voor het werken met metadata.
En er kan een heel redelijke vraag rijzen - waarom zouden InterBase-ontwikkelaars hun systeemgegevens aanpassen aan de gebruikersinterface? Je ziet, interne mechanismen van toegang en leesbewerkingen zouden sneller zijn. Natuurlijk is er veel zin in het bieden van een universeel mechanisme voor het werken met tabellen die metadata beschrijven.
De zaak is dat de logische databasestructuur niet alleen uit tabellen bestaat, maar ook uit andere objecten. Er zijn de volgende objecten in InterBase:
-
Tabel
-
View
-
Trigger
-
Computed_field
-
Validatie
-
Procedure
-
Expression_index
-
Uitzondering
-
Gebruiker
-
Veld
-
Index
-
User-Defined Function (UDF)
We weten nog niet precies de functie van sommige objecten, maar we weten zeker dat ze allemaal moeten worden beschreven en opgeslagen in een vorm die handig is voor de gebruiker en voor toegang vanuit de InterBase-kernel. Het beste zou zijn om deze objecten op te slaan in systeemtabellen. Hun toevoeging en wijziging worden uitgevoerd via SQL-query’s. Een slimme oplossing, nietwaar? De serverimplementatie is volledig gescheiden van een concrete database - alle onderlinge verbindingen worden beschreven door SQL en de uitbreidingen ervan - de taal van opgeslagen procedures en triggers.
Dus alle serverobjecten worden opgeslagen in tabellen. Elk type object heeft een tabel die alle in de database beschreven instanties beschrijft. Voor triggers is er bijvoorbeeld een tabel RDB$Triggers, voor opgeslagen procedures RDB$Procedures, views worden beschreven in tabel RDB$Relations.
Laten we in detail de structuur bekijken van de laatste tabel, die alle tabellen en views in een database beschrijft. De structuur van de tabel RDB$RELATIONS is overgenomen uit Language Reference voor InterBase 6 en wordt hieronder gegeven in tabel 6.
Tabel 6. Systeemtabel RDB$Relations
| Kolomnaam | Datatype | Lengte | Beschrijving |
| RDB$VIEW_BLR | BLOB | 80 | BLR: voor views, bevat BLR (Binary Language Representation) van de query, die InterBase elke keer uitvoert wanneer een view wordt aangesproken. |
| RDB$VIEW_SOURCE | BLOB | 80 | Tekst: voor views, bevat de code van de SQL-query die deze view implementeert. |
| RDB$_DESCRIPTION | BLOB | 80 | Gebruikersbeschrijving van tabel of view |
| RDB$RELATION_ID | SMALLINT | Bevat interne identificatie van tabel/view | |
| RDB$SYSTEM_FLAG | SMALLINT | Definieert het type tabel: gebruikersgegevens - 0; Systeeminformatie > 0. | |
| RDB$DBKEY_LENGTH | SMALLINT | Lengte db$key | |
| RDB$FORMAT | SMALLINT | Gereserveerd voor intern InterBase-gebruik. Bevat wijzigingsteller van metadata voor de gegeven tabel. | |
| RDB$FIELD_ID | SMALLINT | Het aantal velden in de tabel. | |
| RDB$RELATION_NAME | CHAR | 31 | Unieke tabelnaam. |
In de beschrijving van deze systeemtabel zien we een afkorting BLR. Om te begrijpen wat dit is, maken we een uitstapje naar SQL. Zoals bekend zijn views, triggers en opgeslagen procedures code, geschreven in een uitbreiding van de SQL-taal (voor elke DBMS-server zijn er eigen uitbreidingen). Het ligt dicht bij de menselijke taal, waardoor het gemakkelijk is om er query’s in te maken. Maar InterBase vertaalt het uiteraard naar iets meer «machine» - namelijk naar BLR (Binary Language Representation). Elke query, view, trigger, opgeslagen procedure wordt altijd vertaald naar BLR en vervolgens verzonden naar de InterBase-kernel voor uitvoering.
BLR
BLR is een speciale taal, gebruikt als tussenliggende schakel tussen SQL-code die een programmeur schrijft en machinecode die de server accepteert. Niemand schrijft direct in BLR - dat zou nogal moeilijk zijn, omdat voor de hoogst mogelijke uitvoeringssnelheid in deze taal de zogenaamde omgekeerde Poolse notatie wordt gebruikt. Hier is een klein voorbeeld:
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,
BLR voor jouw query’s, procedures, triggers en andere triggers wordt gevormd door de speciale preprocessor die deel uitmaakt van de serverkernel. Zoals getoond in tabel 7, wordt voor views zowel hun tekst (initiële) view als gecompileerde view, dat wil zeggen BLR, opgeslagen. Bij verwijzing naar een object met BLR voert de server de binaire code van het object uit, en hij interpreteert niet elke keer de initiële tekst van deze objecten, wat de uitvoering van ingewikkelde query’s versnelt.
Hiërarchie van objecten in InterBase
Om een duidelijk beeld te krijgen van wat databaseobjecten vertegenwoordigen, zullen we proberen een hiërarchie van databaseobjecten te maken volgens het principe «wie bevat wat». Fysieke pagina’s van databasebestanden zijn het eerste wat in onze hiërarchie moet worden opgenomen als het laagste niveau van gegevensorganisatie. Dan komen tabellen als basisobjecten die alle andere soorten objecten beschrijven. Tabellen beschrijven opgeslagen procedures, triggers, berekende velden, validaties, expressie-indexen, uitzonderingen enzovoort. Let op - alleen beschrijven! Tabellen bevatten alleen declaraties en definities van deze objecten, en objecten worden geïmplementeerd via BLR. Daarom kunnen we tabellen voorstellen als een raamwerk dat alle andere databaseobjecten ondersteunt. BLR zal onderaan het raamwerk staan als de implementatielaag, daarna triggers, opgeslagen procedures, expressie-indexen en views.
Om de specialisten op het gebied van InterBase-interne structuur gerust te stellen, die kunnen tegenwerpen dat BLR van veel objecten (zoals views) in systeemtabellen wordt opgeslagen, merken we op dat deze verhouding nogal moeilijk op een afbeelding is uit te drukken, en voor de eenvoud laten we het weg. Het schema heeft niet het doel om onderlinge afhankelijkheden van databaseobjecten absoluut nauwkeurig weer te geven; het illustreert alleen hun nauwe onderlinge verbinding.
Het feit dat deze objecttypen direct verbonden zijn met BLR, dat ze implementeert zonder enige tussenliggende logica, verenigt ze. Uitzonderingen moeten apart worden toegewezen - ze vertegenwoordigen speciale soorten fouten, gedefinieerd door de gebruiker. Uitzonderingen worden verwerkt op het niveau van de InterBase-kernel en hebben daarom geen BLR. Dergelijke soorten constraints als checks worden boven triggers toegewezen, omdat triggers in werkelijkheid de logica van constraints en checks implementeren.
Een hiërarchie van objecten van de logische en fysieke databasestructuur is afgebeeld op afbeelding 2.]
Afbeelding 10. Objecten van de logische structuur van InterBase-database
Natuurlijk beschrijft dit schema de logische structuur en onderlinge verbindingen van objecten in de database slechts bij benadering en geeft het een algemeen idee ervan. Iedereen die de structuur van InterBase-databasemetadata wil bestuderen, kan re-engineering van database-systeemtabellen uitvoeren en alle onderlinge verbindingen tussen de objecten bekijken, evenals documentatie en InterBase-broncodes raadplegen. Deze tabel toont alleen de belangrijkste databaseobjecten. Laten we kort de belangrijkste functies beschrijven die deze objecten in de database vervullen.
Tabellen - het belangrijkste object, dat gebruikers- en systeemgegevens bevat. Een tabel heeft een unieke naam en bevat een reeks benoemde velden. Een gebruiker kan gegevens in tabellen plaatsen, ophalen en wijzigen. We kunnen zeggen dat een tabel vergelijkbaar is met gewone papieren tabellen die met de hand zijn getekend.
Triggers - uitvoerbare delen van de code, gebruikt voor het implementeren van extra acties op het moment van gegevensbewerkingen. Triggers worden uitgevoerd vóór of na invoeg-, wijzigings- of verwijderingsbewerkingen en maken het mogelijk om waarden in opnieuw aangemaakte records te vervangen en nog veel andere dingen te realiseren.
Een opgeslagen procedure is een krachtig hulpmiddel voor het implementeren van bedrijfslogica op het niveau van de database. Omdat deze op serverniveau wordt uitgevoerd, werkt hij zeer snel en maakt het mogelijk een reeks bewerkingen op gegevenssets uit te voeren. InterBase-opgeslagen procedures retourneren standaard SQL-gegevenssets, waarop alle SQL-bewerkingen kunnen worden uitgevoerd, inclusief unificatie met andere tabellen.
Views zijn gecompileerde SQL-query’s die op de server worden uitgevoerd. Views maken het mogelijk om gegevenssets te organiseren en een deel van de bedrijfslogica naar de server over te dragen.
Validaties zijn beperkingen die worden ingesteld op waarden van velden in de tabel. We kunnen bijvoorbeeld aangeven dat het betreffende veld alleen positieve waarden accepteert. Beperkingen op veldwaarden worden geïmplementeerd door triggers en maken het mogelijk om de referentiële integriteit op databaseniveau effectief te beheren. Gewoonlijk worden beperkingen gebruikt om te voorkomen dat er onjuiste waarden in een tabel worden geplaatst.
Gebruikers - InterBase stelt ons in staat om meerdere gebruikers te hebben voor het werken met de database en om de toegangsrechten tot verschillende databaseobjecten onder hen te verdelen. Zo kunnen we bepalen wie welke databasebewerkingen mag uitvoeren.
Door de gebruiker gedefinieerde functies (UDF) - functies die door de gebruiker zijn gedefinieerd. Dit is een van de krachtigste InterBase-mogelijkheden, waarmee we de standaard SQL-interface kunnen uitbreiden met onze eigen functies. Functies voor het werken met tekst, zoals UPPER (alle tekens in hoofdletters zetten), zijn bijvoorbeeld geïmplementeerd in de standaard UDF-bibliotheek die bij InterBase wordt geleverd. Dankzij de mogelijkheid om eigen UDF’s te maken, kunnen ontwikkelaars de InterBase-functionaliteit praktisch met elke functie uitbreiden. We kunnen elke programmeeromgeving gebruiken die het maken van dynamische bibliotheken mogelijk maakt (Visual C++, C++ Builder, Delphi, enz.) voor het maken van UDF’s.
Conclusie
In dit hoofdstuk hebben we voor het eerst vragen behandeld over de implementatie van het opslaan en verwerken van gegevens binnen een InterBase-database. Helaas kunnen we geen kort overzicht van dit onderwerp geven zonder een groot aantal termen en onnauwkeurige analogieën te gebruiken. Als we de fysieke en logische structuur van de database in meer detail zouden beschrijven, zouden we toch moeten verwijzen naar de primaire codes van InterBase, maar dat zou een ander boek zijn.
Desondanks denken we dat het voor elke programmeur nuttig zou zijn om kennis te maken met de inhoud van het product dat hij dagelijks gebruikt.
Bibliografie
-
«The On-Disk Structure of InterBase» door Ann.W.Harrison
-
«Space Management in InterBase» door Ann W.Harrison
-
«Structure of a Data Page» door Paul Beach (Met dank aan Dave Schnepper en Deej Bredenberg)