Generatoren: Grundlagen

<< Einführung | Firebird Generatoren-Ratgeber | SQL-Befehle für Generatoren >>

Generatoren: Grundlagen

Was ist ein Generator?

Man stelle sich einen Generator vor wie einen "Thread-sicheren" Integer-Zähler, der in einer Firebird Datenbank existiert. Man kann einen Generator erzeugen, in dem man ihm einen Namen gibt:

 CREATE GENERATOR GenTest;

Dann kann man seinen Wert abfragen, erhöhen oder verringern, so wie man es mit einem var i:Integer in Delphi tun kann. Es ist aber nicht immer einfach, ihn zuverlässig auf einen bestimmten Wert zu setzen und diesen dann abzufragen - er ist in der Datenbank, aber ausserhalb jeder Transaktionskontrolle.

zurück zum Seitenanfang

Was ist eine Sequenz?

Sequence ist der offizielle SQL-Begriff für das, was Firebird einen Generator nennt. Da Firebird ständig in Richtung besserer Unterstützung des SQL-Standards entwickelt wird, kann man von Firebird 2 an den Begriff SEQUENCE als Synonym für GENERATOR benutzen. Es wird in der Tat empfohlen, in neuem SQL-Code die SEQUENCE-Syntax zu verwenden.

Auch wenn das Schlüsselwort SEQUENCE die Betonung auf die Erzeugung einer Serie von Werten legt, während GENERATOR eher auf die "Fabrik" zur Erzeugung der Werte zu deuten scheint, gibt es keinerlei Unterschied in Firebird zwischen einer SEQUENCE und einem GENERATOR. Es sind einfach zwei Namen für das gleiche Datenbankobjekt. Man kann einen Generator erzeugen und dann mit der Sequenz-Syntax auf ihn zugreifen und umgekehrt.

Dies ist die bevorzugte Syntax zur Erzeugung einer Sequenz bzw. eines Generators in Firebird 2:

 CREATE SEQUENCE SeqTest;

zurück zum Seitenanfang

Wo werden Generatoren gespeichert?

Die Deklarationen von Generatoren werden in der Systemtabelle RDB$GENERATORS abgelegt. Ihre aktuellen Werte hingegen liegen in speziell reservierten Seiten (Pages) in der Datenbank. Man bearbeitet diese Werte nie direkt, sondern durch eingebaute Funktionen und Befehle, die im Verlauf dieses Artikels besprochen werden.

Warnung: Die Informationen in diesem Abschnitt sind nur zu Lernzwecken angegeben. Grundsätzlich sollte man nie direkt mit den Systemtabellen arbeiten. Versuche nicht, Generatoren zu erzeugen oder zu ändern, in dem du direkt die Systemtabelle RDB$GENERATORS manipulierst. (Ein SELECT kann allerdings nicht schaden).

Die Struktur der RDB$GENERATORS Systemtabelle sieht wie folgt aus:

  • RDB$GENERATOR_NAME CHAR(31)
  • RDB$GENERATOR_ID SMALLINT
  • RDB$SYSTEM_FLAG SMALLINT

Und, von Firebird 2.0 an:

  • RDB$DESCRIPTION BLOB subtype TEXT

Man beachte, dass die GENERATOR_ID – wie der Name schon sagt – ein IDentifizierer für jeden Generator ist, und nicht sein Wert. Man sollte auch nicht diese ID in seinen Anwendungen benutzen, um später auf Generatoren zuzugreifen. Abgesehen davon, dass dies wenig Sinn macht (der Name identifiziert den Generator), kann sich die GENERATOR_ID nach einem Backup und anschliessendem RESTORE ändern. Das SYSTEM_FLAG ist 1 für die in der Datenbank intern verwendeten Generatoren, und NULL oder 0 für alle selbsterzeugten.

Werfen wir einen Blick auf die RDB$GENERATORS-Tabelle, hier mit einem einzigen selbstdefinierten Generator:

RDB$GENERATOR_NAMERDB$GENERATOR_IDRDB$SYSTEM_FLAG
RDB$SECURITY_CLASS11
SQL$DEFAULT21
RDB$PROCEDURES31
RDB$EXCEPTIONS41
RDB$CONSTRAINT_NAME51
RDB$FIELD_NAME61
RDB$INDEX_NAME71
RDB$TRIGGER_NAME81
MY_OWN_GENERATOR9NULL

Firebird 2 Anmerkungen:

  • Firebird 2 hat einen neuen System-Generator eingeführt namens RDB$BACKUP_HISTORY. Dieser wird vom neuen NBackup-Feature verwendet.
  • Auch wenn die SEQUENCE-Syntax jetzt bevorzugt wird, wurden die RDB$GENERATORS Systemtabelle und ihre Spalten in Firebird 2 nicht umbenannt.

zurück zum Seitenanfang

Was ist der maximale Wert eines Generators?

Generatoren speichern und liefern 64-Bit Integerwerte in allen Firebird-Versionen. Dies ergibt einen Wertebereich von:

-263 .. 263-1 oder -9,223,372,036,854,775,808 .. 9,223,372,036,854,775,807

Würde man also einen Generator mit Startwert 0 benutzen, um damit eine NUMERIC(18) oder BIGINT-Spalte zu befüllen, und man würde 1000 neue Datensätze pro Sekunde anlegen, dann würde es etwa 300 Millionen Jahre (!) dauern bevor der Generator überläuft. Da es eher unwahrscheinlich ist, dass die Menschheit dann noch auf diesem Planeten herumläuft (und immer noch Firebird-Datenbanken einsetzt), braucht man sich darüber also nicht wirklich Gedanken machen.

Hier aber ein Wort der Warnung: Firebird spricht zwei SQL-"Dialekte": Dialekt 1 und Dialekt 3. Neue Datenbanken sollten immer mit dem in vieler Hinsicht mächtigeren Dialekt 3 erstellt werden. Dialekt 1 dient nur der Abwärtskompatibilität für Datenbanken, die mit InterBase® 5.6 und früheren Versionen erstellt wurden.

Einer der Unterschiede zwischen den beiden liegt darin, dass Dialekt 1 keinen nativen 64bit-Integer-Typen kennt. NUMERIC(18)-Spalten beispielsweise werden intern als DOUBLE PRECISION abgespeichert, was aber ein Gleitkommawert ist. Der größte verfügbare Integer-Typ in Dialekt 1 ist der 32-Bit-Integer.

In Dialekt 1 wie auch in Dialekt 3 haben Generatoren 64bit. Wenn man aber einen Generatorwert in einer Dialekt 1-Datenbank einer INTEGER-Spalte zuweist, werden die oberen 32bit abgeschnitten, so dass man einen effektiven Wertebereich erhält von:

-231 .. 231-1 oder -2,147,483,648 .. 2,147,483,647

Auch wenn der Generator selbst von 2,147,483,647 zu 2,147,483,648 und weiterläuft, würde der abgeschnittene Wert in der Spalte an dieser Stelle überlaufen und den Eindruck eines 32-Bit-Generators erwecken.

In der oben beschriebenen Situation mit 1000 Datensätzen pro Sekunde würde die vom Generator gefüllte Spalte nun nach 25 Tagen (!!!) überlaufen, und dem sollte auf jeden Fall Beachtung geschenkt werden. 231 ist eine ganze Menge, aber je nach Situation auch wieder nicht so viel.

Anmerkung: In Dialekt 3 geht die Zuweisung von Generator-Werten an INTEGER-Spalten solange gut, wie der Wert im 32-Bit-Integer-Bereich liegt. Sobald aber dieser Bereich überschritten wird, gibt es einen Numerischen Überlaufsfehler ("numeric overflow error"): Dialekt 3 ist viel strikter in der Bereichsüberprüfung als Dialekt 1!

Client-Dialekte und Generatorwerte

Clients, die mit einem Firebird-Server verbunden sind, können ihren Dialekt auf 1 oder 3 stellen, und zwar unabhängig von der verbundenen Datenbank. Es ist der Dialekt des Clients, nicht der der Datenbank, der entscheidet, wie Firebird Generatorwerte zum Client liefert:

  • Wenn der Client-Dialekt 1 ist, liefert der Server Generatorwerte als abgeschnittene 32-Bit-Werte zum Client. Aber innerhalb der Datenbank bleiben sie 64-Bit-Werte und laufen nach Erreichen von 231-1 nicht über (auch wenn das für den Client so aussieht). Dies gilt sowohl für Dialekt 1 wie für Dialekt 3-Datenbanken.
  • Wenn der Client-Dialekt 3 ist, gibt der Server volle 64 Bit zum Client zurück. Auch dies gilt für beide Datenbank-Dialekte.

zurück zum Seitenanfang

Wie viele Generatoren sind in einer Datenbank verfügbar?

Seit Firebird 1.0 ist die Anzahl der verfügbaren Generatoren nur durch den größtmöglichen Wert für die ID-Spalte in der RDB$GENERATORS-Systemtabelle limitiert. Da dies eine SMALLINT-Spalte ist, ist die max. Anzahl 215-1 oder 32767. Die erste ID ist immer 1, d.h. die Gesamtanzahl der Generatoren kann 32767 nicht überschreiten. Wie zuvor beschrieben, gibt es in jeder Datenbank 8 oder 9 Systemgeneratoren, so dass effektiv noch mindestens 32758 für eigene Generatoren übrig bleiben. Dies sollte für jede praktische Anwendung bei weitem ausreichen. Da die Anzahl der Generatoren keine Auswirkung auf die Performanz hat, kann man nach Herzenslust so viele Generatoren benutzen wie man möchte.

Ältere InterBase®- und Firebird-Versionen

In den frühesten vor-1.0 Firebird-Versionen, so wie in InterBase®, wurde nur eine Datenbankseite (Page) zur Speicherung der Generatorwerte benutzt. Dadurch war die Anzahl nutzbarer Generatoren durch die Seitengröße (Page Size) der Datenbank begrenzt. Die folgende Tabelle zeigt, wie viele Generatoren (inkl. der Systemgeneratoren) in den verschiedenen InterBase®- und Firebird-Versionen zur Verfügung stehen (mit Dank an Paul Reeves für diese Informationen):

VersionSeitengrösse (Page size)
 1K2K4K8K
InterBase® < v.624750310152039
InterBase® 6 und frühe Prä-1.0 Firebird1232515071019
Alle späteren Firebird-Versionen32767

In InterBase®-Versionen vor 6 waren Generatoren nur 32 Bit breit. Dies erklärt, warum diese früheren Versionen ungefähr die doppelte Anzahl an Generatoren in der selben Seitengrösse speichern konnten.

Warnung: InterBase®, zumindest bis inklusive Version 6.01, ließ problemlos die Erzeugung von bis zu 32767 Generatoren zu. Was passierte, wenn man auf Generatoren mit einer ID grösser der oben angegeben Maximalzahl zugriff, hing von der Version ab:

  • InterBase® 6 generierte einen "invalid block type"-Fehler da die berechnete Position ausserhalb der einen reservierten Generatoren-Seite lag.
  • In früheren Versionen wurde ein Fehler gemeldet, wenn die berechnete Position ausserhalb der Datenbank lag. Ansonsten wurde beim Lesezugriff einfach der Wert geliefert, der sich zufällig an der berechneten Position befand. Wurde der "zu grosse" Generator verändert, dann überschrieb er einfach den Wert an der berechneten Position. Manchmal führte dies zu einem sofortigen Fehler, meistens aber einfach zu einer stillen Beschädigung der Datenbank.

zurück zum Seitenanfang

Generatoren und Transaktionen

Wie gesagt leben Generatoren ausserhalb der Transaktionskontrolle. Dies bedeutet schlicht und ergreifend, dass es keinen sicheren Weg gibt, in einer Transaktion ein "Rollback" eines Generators durchzuführen. Andere, zeitgleich laufende Transkationen können den Wert verändern, während die eigene Transaktion läuft. Hat man also einen Generatorwert erzeugt, sollte man ihn als "auf ewig verbraucht" betrachten.

Startet man also eine Transaktion und erzeugt darin einen Generatorwert von - sagen wir - 5, dann bleibt der Generator auf diesem Wert, selbst wenn man ein Rollback der Transaktion durchführt (!). Man sollte nicht mal denken an etwas wie: "OK, wenn ich ein Rollback durchführe, setze ich den Generator mittels GEN_ID(mygen,-1) eben wieder auf 4 zurück". Dies kann meistens funktionieren, ist aber unsicher, da andere Transaktionen den Wert inzwischen wiederum verändert haben können. Aus dem gleichen Grund macht es keinen Sinn, den aktuellen Generatorwert mit GEN_ID(mygen,0) aus der Datenbank zu holen und ihn dann Client-seitig zu inkrementieren.

zurück zum Seitenanfang
<< Einführung | Firebird Generatoren-Ratgeber | SQL-Befehle für Generatoren >>