Was man sonst mit Generatoren machen kann

<< Generatoren zum Erzeugen eindeutiger Datensatz-IDs | Firebird Generatoren-Ratgeber | A: Dokumentenhistorie >>

Was man sonst mit Generatoren machen kann

Hier gibt es noch ein paar Anregungen für den Gebrauch von Generatoren für andere Zwecke als das Erzeugen von Datensatz-IDs.

Generatoren verwenden, um z.B. Transferdateien eindeutig zu kennzeichnen

Eine "klassische" Anwendung von Generatoren ist es, eindeutige, aufeinanderfolgende Werte zu erzeugen für - na ja, alles in der Applikation, abgesehen von den oben diskutierten Datensatz-IDs. Exportiert die Anwendung z.B. Daten zu anderen Systemen, kann man Generatorwerte sicher zur eindeutigen Bezeichnung jedes Transfers benutzen. Dies hilft enorm bei der Fehlersuche in solchen Datenschnittstellen (und anders als die meisten der folgenden Anwendungen funktioniert es sicher und robust).

Generatoren als "Benutzungszähler" für Prozeduren als Statistikgrundlage

Stelle dir vor, du hast gerade ein fantastisches neues Feature mittels einer Stored Procedure erstellt. Jetzt spielst du die neue Version beim Kunden ein und möchtest später wissen, ob und wie oft die Kunden dieses Feature wirklich benutzen. Simpel: man nehme einen speziellen Generator, der nur in dieser Prozedur hochgezählt wird, und das war`s... mit der kleinen Einschränkung, dass man nicht wissen kann, wie viele Transaktionen mit Aufrufen der SP durch ein Rollback nicht zu Ende gebracht wurden. In jedem Falle aber weiss man dann, wie oft Benutzer versucht haben, die SP zu benutzen. :-)

Man könnte diese Methode noch verfeinern, in dem man zwei Generatoren benutzt: einer wird direkt am Start der Prozedur erhöht, der zweite ganz am Ende vor dem EXIT. Haben beide nach einer Zeit den selben Wert, dann ist innerhalb der SP nie etwas schiefgegangen etc. Natürlich weiss man immer noch nicht, wieviele SP-Aufrufe einem Rollback der aufrufenden Transaktion zum Opfer gefallen sind.

zurück zum Seitenanfang

Generatoren zur Simulation von Select count(*) from...

Es ist ein bekanntes Problem von InterBase und Firebird, dass ein SELECT COUNT(*) (ohne WHERE-Klausel) bei einer wirklich grossen Tabelle eine ganze Weile zur Durchführung benötigt, da der Server "zu Fuss" durchzählen muss, wie viele Sätze sich zum Zeitpunkt des Aufrufs gerade in der Tabelle befinden (Stichwort: Multigenerationsarchitektur). Theoretisch liesse sich dieses Problem einfach durch den Einsatz von Generatoren umgehen:

  • man nehme einen speziellen "Satzzähler"-Generator;
  • man erzeuge einen BEFORE INSERT Trigger, der ihn erhöht
  • und einen AFTER DELETE Trigger, der ihn wieder runterzählt.

Das funktioniert wunderbar und macht ein "volles" Durchzählen der Datensätze überflüssig - man fragt einfach den aktuellen Generatorwert ab. Die Betonung liegt hier auf theoretisch, denn das ganze geht den Bach runter, sobald Insert-Befehle schiefgehen, denn wie gesagt liegen Generatoren ausserhalb jeder Transaktionskontrolle. INSERT-Befehle können durch Constraints (eindeutige Index-Verletzungen, NOT NULL-Felder enthalten NULL etc.) oder durch andere Metadaten-Einschränkungen schiefgehen, oder einfach weil die aufrufende Transaktion mit einem Rollback endet. Man hat keine Datensätze in der Tabelle und trotzdem steigt der Zähler-Generator.

Es kommt also drauf an - wenn man den ungefähren Prozentsatz schieflaufender INSERTs kennt (man kann dafür ein "Gefühl" entwickeln), und es nur um eine grobe Abschätzung der Anzahl der Datensätze geht, dann kann diese Methode hilfreich sein, obwohl sie nicht exakt ist. Von Zeit zu Zeit kann man ein "normales" Durchzählen der Sätze durchführen, um den Generator wieder auf den richtigen Wert zu setzen ("Re-Synchronisation" des Generators), so dass man den Fehler in Grenzen halten kann.

Es gibt Situationen, wo Kunden glücklich leben können mit einer Aussage wie "es gibt ungefähr 2,3 Millionen Datensätze in der Tabelle", die sie sofort auf einen Mausklick hin erhalten, einen aber erschiessen würden, wenn sie 10 Minuten oder mehr warten müssen, um zu erfahren, dass es exakt 2.313.498.229 Datensätze sind...

zurück zum Seitenanfang

Generatoren zum Überwachen und/oder Steuern lange laufender Stored Procedures

Hat man Stored Procedures, die z.B. Auswertungen auf grossen Tabellen oder über komplexe Joins fahren, dann können diese ganz schön lange brauchen. Hier können Generatoren auf zweierlei Weise helfen: Sie können einen Fortschrittszähler liefern, den man zyklisch vom Client aus abfragen kann, und sie können benutzt werden, um die Ausführung abzubrechen:

 CREATE GENERATOR gen_spTestProgress;
 CREATE GENERATOR gen_spTestStop;

 set term ^;

 CREATE PROCEDURE spTest (...)
 AS
 BEGIN
   (...)
   for select <viele Daten die lange zur Auswertung brauchen>
   do begin
     GEN_ID(gen_spTestProgress,1);

     IF (GEN_ID(gen_spTestStop,0)>0) THEN Exit;

     (...hier die normale Abarbeitung...)
   end
 END^

Nur ein grober Entwurf, aber das Konzept sollte erkennbar sein. Von der Client-Seite aus kann man ein GEN_ID(gen_spTestProgress,0) asynchron zur Ausführung der Prozedur aufrufen (z.B. in einem zweiten Thread), um zu sehen, wie viele Sätze bereits abgearbeitet sind, und diesen Wert in einem Fortschrittsbalken anzeigen. Und man kann mittels GEN_ID(gen_spTestStop,1) die Prozedur jederzeit von "aussen" abbrechen.

Auch wenn dies sehr hilfreich sein kann, hat es eine starke Einschränkung: Es ist nicht sicher im Mehrbenutzer-Betrieb. Würde die Prozedur parallel von zwei Transaktionen aus aufgerufen, würde der Fortschrittszähler gestört - beide Aufrufe würden den gleichen Generator erhöhen, und das Resultat wäre unbrauchbar. Schlimmer noch, eine Inkrementierung des STOP-Generators würde die Prozedur in beiden Transaktionen beenden. Aber für z.B. Monatsauswertungen, die von einem einzigen Modul im Batch-Betrieb gefahren werden, kann dies akzeptabel sein - wie üblich hängt es von den Randbedingungen ab.

Will man diese Technik einsetzen, um vom Benutzer jederzeit aufrufbare Prozeduren zu steuern, muss durch andere Mechanismen gewährleistet werden, dass die SP nicht zeitgleich mehrmals ausgeführt werden kann. Darüber sinnierend kam mir die Idee, dafür einen weiteren Generator einzusetzen: nennen wir ihn gen_spTestLocked (unter Annahme des Startwerts von 0 natürlich):

 CREATE GENERATOR gen_spTestProgress;
 CREATE GENERATOR gen_spTestStop;
 CREATE GENERATOR gen_spTestLocked;

 set term ^;

 CREATE PROCEDURE spTest (...)
 AS
 DECLARE VARIABLE lockcount INTEGER;
 BEGIN
   lockcount = GEN_ID(gen_spTestLocked,1);
     /* allererster Schritt: Erhöhen des Blockade-Generators */

   if (lockcount=1) then /* _wir_ haben die Sperre, weitermachen */
   begin
     (.. .hierdernormaleProzedurrumpf...
   end

   lockcount = GEN_ID(gen_spTestLocked,-1); /* Erhöhung rückgängig machen */

   /* sicherstellen dass der Generator auch bei Ausnahmen (Exceptions) im
      Prozedurrumpf jederzeit sauber zurückgesetz wird: */

   WHEN ANY DO
     lockcount = GEN_ID(spTestLocked,-1); /* s.o. */
   exit;
 END^

Hinweis: Ich bin mir nicht 100%ig sicher, ob dies im Mehrbenutzerbetrieb jederzeit sauber funktioniert, aber es sieht recht "schusssicher" aus - so lange kein EXIT im normalen Prozedurrumpf vorkommt, denn dann würde die Prozedur mittendrin verlassen werden und der Blockade-Generator würde inkrementiert stehenbleiben. Die WHEN ANY-Klausel behandelt Ausnahmen, aber keine normalen EXITs. Dann müsste man den Generator von Hand zurücksetzen - aber man könnte ihn auch im Prozedurrumpf direkt vor dem EXIT herunterzählen. Mit den geeigneten Sicherheitsvorkehrungen fällt mir keine Situation ein, wo dieser Mechanismus fehlschlagen würde - falls dir eine einfällt, lass es uns wissen!

Siehe auch:
deutschsprachig:
Generator
Stored Procedure
Trigger

zurück zum Seitenanfang
<< Generatoren zum Erzeugen eindeutiger Datensatz-IDs | Firebird Generatoren-Ratgeber | A: Dokumentenhistorie >>