Cursor

Aus TRENZ PartnerNet
Wechseln zu: Navigation, Suche

Ein Cursor ist eine Kontrollstruktur in der Datenbank-Programmierung ähnlich einer Schleife, jedoch stets in Bezug auf eine Wertemenge. Jedes Element der Menge wird einzeln bearbeitet. Dies widerspricht dem sonstigen Ablauf eines Datenbank-Systems, das bevorzugt mit Mengen als Ganzes arbeitet — und dabei auch deutlich schneller ist. Ein Cursor sollte daher vermieden werden (insbesondere dann, wenn er Teil eines Triggers ist: ein Trigger läuft grundsätzlich in einer Transaktion; dauert diese sehr lange, so kann dies zu Deadlock-Situationen führen).

Cursor lassen sich dennoch nicht immer vermeiden. Einige Konstrukte in easyLogic nehmen an, dass man nur mit einem Container arbeitet, und sich UPDATE-Statements somit auch nur auf einen Eintrag in einer zz-Tabelle auswirken — was zumeist auch der Fall ist.

Anwendungs-Beispiel

Durch Klick eines Buttons wird eine Stored Procedure mit der ID des offenen Containers aufgerufen. Dadurch lässt sich der Prozess, den die Prozedur durchführt, nicht für mehrere Container gleichzeitig durchführen, denn es können nicht mehrere IDs übergeben werden. Ohne die Prozedur umzubauen, kann man das Problem — insbesondere für einmalige Korrekturen geeignet — mit einem Cursor lösen:

DECLARE @ContainerID int -- eine lokale Variable, die die aktuelle Container-ID beinhaltet

/*
 * FAST_FORWARD kombiniert die Eigenschaften FORWARD_ONLY und READ ONLY, was
 * für einen Geschwindigkeits-Schub sorgt. FORWARD_ONLY legt fest, dass der
 * Cursor nur in einer Richtung arbeiten kann. READ ONLY verlässt sich darauf,
 * dass sich die Datenmenge, über die iteriert wird, während der Ausführung
 * des Cursors nicht ändert.
 *
 * Der fiktive Button ist im Container-Typ 1 aufzufinden, also iterieren wir
 * über alle Container diesen Typs.
 */
DECLARE ButtonClickCursor CURSOR FAST_FORWARD FOR
   SELECT id FROM container WHERE Typ = 1

OPEN ButtonClickCursor

/* Hier beginnt die eigentliche Iteration. Wir können wegen der Einstellung
 * FORWARD_ONLY nur den jeweils nächsten Eintrag holen.
 */
FETCH NEXT FROM ButtonClickCursor INTO @ContainerID

WHILE @@fetch_status <> -1 -- prüft, ob die Datenmenge erschöpft ist
BEGIN
   EXEC ButtonClick @ContainerID -- das Klicken des Buttons wird hier simuliert

   FETCH NEXT FROM ButtonClickCursor INTO @ContainerID
END

CLOSE ButtonClickCursor
DEALLOCATE ButtonClickCursor

Optionen

  • FAST_FORWARD, wie in obigem Beispiel verwendet, bietet sich bei beinahe jedem Cursor an. Zu Gunsten eines kräftigen Performance-Schubs kombiniert diese Option FORWARD_ONLY, wodurch man nicht mehr in mehrere Richtungen iterieren kann (z.B. mit FETCH PRIOR), und READ ONLY, mit dem Änderungen an der Menge während der Cursor-Ausführung nicht berücksichtigt werden.
  • LOCAL sorgt dafür, dass ein Trigger nur innerhalb eines Skriptes — z.B. eines Triggers — gültigt ist. DEALLOCATE findet damit implizit statt und entfällt.

Performance

Man sollte stets versuchen, ohne einen Cursor auszukommen. Selbstverständlich sollte auch die Menge, über die iteriert wird, klein gehalten werden — wann immer es möglich ist, sollten einschränkende Prüfungen also bereits außerhalb des Cursors stattfinden.

Meine Werkzeuge