Frage:

Muss man bei der Verwendung von SQL für Datenbankzugriffe die Dateien im Journal aufzeichnen?

Antwort:

Ohne die Aktivierung der Journal-Aufzeichnung für die entsprechenden Dateien arbeitet DB2 400 ohne jegliche Satzsperren bei Datenbankzugriffen mittels SQL. Daraus folgt unmittelbar, dass schreibende Zugriffe nur durchgeführt werden sollen, wenn die Dateien im Journal eingetragen werden. Selbst für nur lesende Zugriffe empfiehlt sich das Sperren von Sätzen, wenn zum Beispiel nur gültige Zustände in eine Auswertung einbezogen werden sollen.

Das Sperrkonzept von SQL auf der AS/400 unterscheidet sich grundsätzlich von dem, was man auf dieser Plattform vom satzweisen Zugriff unter RPG oder COBOL kennt. Beim nativen Zugriff aus RPG oder COBOL wird der Mechanismus für die Satzsperren beim Öffnen der Dateien festgelegt. Unter SQL fällt ein Öffnen von Dateien weg – und damit ist dieses Konzept nicht mehr anwendbar.

Die zentralen Begriffe zum Verständnis des Sperrkonzeptes von SQL sind hierbei die Connection und das Transaction Level, die aufeinander Bezug nehmen. Unter Connection versteht man hierbei eine Verbindung zur Datenbank und das Transaction Level ist eine Eigenschaft der Verbindung, über die man steuert, welche Sätze gesperrt werden.

Dass das Einfache manchmal komplizierter sein kann, erkennt man hierbei wieder einmal auf der AS/400, bei ILE Programmen, die per SQL auf die lokale Datenbank zugreifen. Vordergründig erscheint es eine Vereinfachung, dass man für die lokale Datenbank zum Herstellen der Verbindung kein CONNECT-Statement absetzen muss, sondern dass dies automatisch erfolgt und zwar im ersten Programm, das SQL verwendet. „Automatisch“ zieht aber nach sich, dass man nur eine Verbindung haben kann, denn jedes Mal eine neue zu erstellen, scheidet aus Performance-Gründen aus. In vielen Fällen braucht man aber gerade wegen der Transaktions-Steuerung mehr als eine Verbindung. Mit der Einführung von ILE hat man also diese Einschränkung relativiert, indem man pro Aktivierungsgruppe (ACTGRP) eine Verbindung zur lokalen Datenbank haben kann. Wenn man nichts unternimmt, hat bei ILE-Programmen auch jede Activation Group ihre Verbindung zur lokalen Datenbank automatisch, wenn sie diese denn benötigt. Einstellen kann man dies beim manuellen Starten von Commit (STRCMTCTL), was nur vor dem automatischen Start beim Aufruf des ersten SQL-Programms im Job möglich ist.

Im Falle des Zugriffs auf die Datenbank eines Servers, sei es von einem RPG- oder von einem COBOL-Programm einer anderen AS/400 oder von einem Java-Programm eines beliebigen Rechners im Netzwerk, erstellt man sich einfach so viele Connections, wie man braucht, oder entnimmt sich aus einem Pool für jede Transaktion eine neue. Der Pool sorgt dafür, dass nicht unnötig ständig neue Verbindungen erzeugt werden müssen.

Für jede Verbindung kann nun einzeln festgelegt werden, wie die Satzsperren gehandhabt werden sollen. Dies kann bei den Erstellungsbefehlen (CRTSQLxxx), beim Starten von SQL (STRSQL, RUNSQLSTM), beim Starten von Commit (STRCMTCTL), oder aus dem Programm gesteuert erfolgen. Die entsprechenden OS/400-Befehle verwenden hier den Parameter „Lock Level“ (LCKLVL).

Am sichersten steuert man das Ganze aus dem Programm, da weiß man dann immer, welche Sperrstufe man hat. SQL verwendet dazu die Anweisung SET TRANSACTION ISOLATION LEVEL . Für Sperrstufe setzt man dann das gewünschte Sperrverhalten ein. Bei der Erstellung der SQL-Programme kann man dann den Unterlassungswert *CHG unverändert übernehmen.

Journalisierung der Dateien ist nun für alle Sperrstufen außer „No Commit“, das in OS/400 als *NONE bezeichnet wird, erforderlich. Nur bei Lock Level *NONE wird kein Commitment Control gestartet und es werden keinerlei Sätze gesperrt – weder beim Lesen noch beim Schreiben, Löschen und Ändern. Bei allen anderen Sperrstufen wird bei SQL-Zugriffen Commitment Control nötigenfalls automatisch gestartet – und dafür ist die Journalisierung aller beteiligten Dateien erforderlich.

Mit der Transaktions-Stufe „No Commit“ werden weder gelesene noch geschriebene Sätze gesperrt. Man bekommt also Sätze in Änderung gezeigt, was so genannte Dirty Reads – das Lesen inkonsistenter Zustände – zur Folge haben kann. Zudem lässt es sich nicht verhindern, dass Sätze zwischen dem Lesen und der Fortschreibung von einer anderen Transaktion verändert oder gar gelöscht werden können. Mit dieser Art der Lesezugriffe, lassen sich allenfalls Subfile-Anzeigen füllen, bei denen vor weiterer Verarbeitung ohnehin neu und dann hoffentlich richtig gelesen werden muss.

Die nächst höhere Einstellung der Sperrstufe auf *CHG dient eigentlich lediglich dazu, für das Starten der Commit-Steuerung zu sorgen, indem der automatische Start ermöglicht wird. In dieser Stufe werden alle geschriebene Sätze gesperrt gehalten – dies dann allerdings bis zur expliziten Freigabe mit der SQL-Anweisung Commit oder mit Rollback. Die Commit-Anweisung gibt alle Sperren der Connection frei, während die Rollback-Anweisung zusätzlich alle Änderungen zurückdreht; dafür wird dann auch das Journal, die laufende Chronologie aller Änderungen, benötigt. Mit dieser Einstellung gewinnt man nichts an Sicherheit, auch hier wird wahllos alles gelesen. Satzänderungen werden nicht sicherer, sondern eher ungewisser. Endet die Verbindung zur Datenbank beim Ende des Prozesses, ohne dass man die Transaktion committed hat, so werden alle Änderungen automatisch zurück genommen.

Ab der Sperrstufe „Read Committed“ wird es interessanter und sicherer. Sätze werden nur gelesen, wenn sie auch commited sind. Das bedeutet, dass man nur gültige Zustände präsentiert bekommt. Die Leseoperation mit SELECT INTO sperrt allerdings nicht, sodass bei erneutem Lesen innerhalb der Transaktion ein geänderter Satz gelesen werden könnte; dies nennt man: Non Repeatable Read. Eine weitere Anomalie – der so genannte Phantom Read – kann hier ebenfalls noch auftreten. Unter einer so genannten Phantomzeile versteht man hierbei einen gelesenen Datensatz, der zwischen zwei Lesevorgängen innerhalb einer Transaktion eingefügt wurde. Die Sperrstufe „Read Committed“ lässt sich bevorzugt für Auswertungen verwenden. Arbeitet man hier mit einem Cursor, den man in Java ResultSet nennt, so kann man hiermit schon durch Sperren eines gelesenen Satzes geschützte Updates vornehmen.

Die Transaktionsstufe „Repeatable Read“ verhindert nun schon zusätzlich zu den niedrigeren Sperrstufen „Non Repeatable Reads“. Dies wird erreicht durch Sperren aller Sätze, die gelesen wurden während der Transaktion. Dieses Verhalten ist optimal für Datenänderungen in einer Transaktion, aber bereits gefährlich für Lese-Operationen. Ein SELECT MAX(KundenNummer) FROM KUNDE sperrt hier bereits die gesamte Datei. Beim Füllen eines Subfiles müssen hierbei deshalb nach jedem Satz die Sperren mit Commit freigegeben werden.
Vom SQL-Standard wird eigentlich die Sperrstufe „Serializable“ als Unterlassungswert gefordert. Diese ist im DB2/400 in den meisten Konstellationen nicht verwendbar, da sie mit Sperren auf die Dateien arbeitet. Diese Sperren sind dann mit konkurrierendem Record Level Access nicht verträglich. Unglücklicherweise wird für diese Stufe auch das Kürzel *RR und RR verwendet, das fälschlicherweise an Repeatable Read erinnert, aber Serializable meint.

Die einfachste Vorgehensweise – mit einem guten Kompromiss zwischen Sicherheit und Sperren – besteht nun darin. erst mal alle Dateien im Journal einzutragen. Das ist auch hilfreich zur Fehlersuche und erfordert keinerlei Änderungen an vorhandenen Anwendungen. Die Bedenken in Bezug auf Performance sind längst überholt, es sei denn, Sie haben eine /38 (es gibt Leser, die kennen so was noch) oder eine Plattenauslastung von 95 Prozent.

Bei der Erstellung der SQL-Programme lässt man den Unterlassungswert für die Sperren unverändert. In allen Programmen, die Subfiles füllen, braucht man sich um Transaktions-Steuerung nicht zu kümmern; da geht alles von selber seinen richtigen Gang. Will man alle Probleme vermeiden, sagt man fleißig COMMIT nach jedem Read.

Interessanter wird es schon für Auswertungen, schließlich sollen Lagerbestände und Buchungssalden ja stimmen und nicht gerade mal ein größerer Posten zwischen zwei Lägern verschwinden (wie in manchem RPG-Programm ohne Commit und SQL).

/* Start neue Transaktion

C/EXEC SQL COMMIT
C/END-EXEC
C/EXEC SQL SET TRANSACTION ISOLATION LEVEL READ COMMITTED
C/END-EXEC
/*============================================================
C CALLP KonsistenteAuswertung
/*============================================================
C/EXEC SQL COMMIT
C/END-EXEC
C/EXEC SQL SET TRANSACTION ISOLATION LEVEL CHG
C/END-EXEC
/* Ende der Transaktion

Zu Beginn wird mit Commit eine neue Transaktion gestartet. Dies ist auch erforderlich, damit man die Sperrstufe überhaupt ändern kann. Dann wird die Auswertung erstellt; durch die Wahl von Read Committed wird vermieden, dass ein SELECT MAX(?) ganze Dateien sperrt. Nach Erstellung der Auswertung wird dann die Sperrstufe wieder zurück gestellt.

Für Datenänderungen und andere Schreiboperationen empfiehlt sich ein höheres Maß an Transaktions-Sicherheit. Hier wird zu Beginn einer neuen Transaktion die Sperrstufe auf Repeatable Read eingestellt, was keinesfalls mit RR verwechselt werden darf. Dann erfolgt eine ändernde Transaktion, die aus mehreren Schreiboperationen bestehen kann und anschließend die Beendigung der Transaktion sowie das Rückstellen der Sperrstufe. Selbstverständlich ist es sinnvoll und wichtig, bevor man Commit sagt, zu prüfen, ob ein Rollback angebrachter wäre. In SQL erfolgt die dazu erforderliche Fehlerabfrage über den SQL-Code oder den SQL-State.

/* Start neue Transaktion

C/EXEC SQL COMMIT
C/END-EXEC
C/EXEC SQL SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
C/END-EXEC
/*============================================================
C CALLP SchreibeEineTransaktion
/*============================================================
C/EXEC SQL COMMIT
C/END-EXEC
C/EXEC SQL SET TRANSACTION ISOLATION LEVEL CHG
C/END-EXEC
/* Ende der Transaktion

Jetzt ist nur noch darauf zu achten, dass während einer Transaktion keine anderen Programme aufgerufen werden und insbesondere keine Bildschirme ausgegeben werden, damit keine Satzsperren hängen bleiben können.

Die Themen „Aktivierungsgruppen“ und „SQL-Ressourcen“ beim Commit (haben Sie schon mal einen beschädigten Cursor gehabt?) seien in diesem Beitrag erst mal ausgeklammert.

Den Autor Dieter Bender erreichen Sie unter dieter.bender@midrangemagazin.de