Optimierung der DB2 Performance in JDBC & SQLJ 1 Optimierung der Performance - Übersicht Wie bereits diskutiert, läuft "static SQL", mit Ausnahme einier weniger Fälle schneller als "dynamic SQL" in JDBC. Die folgenden Abschnitte beschreiben eine Reihe von Technologien, die man anwenden sollte, um die Performance von DB2 im Umfeld von SQLJ oder JDBC zu sichern. 1.1 Ausschalten von AutoCommit Verwendet man JDBC, um eine Verbindung aufzubauen, so wird das "AutoCommit feature" für die Datenbank per default eingeschaltet. AutoCommit, wie der Name schon sagt, "commited" jedes SQL Statement, das an die Datenbank abgesetzt wurde. Diese automatische Funktion kann den Aufwand an SQL, das zu kodieren wäre, reduzieren, aber es vermindert auch die gesamte Antwortzeit von DB2, da jedes Statement bei seiner Ausführung zusätzlichen "overhead" verursacht. Das "AutoCommit feature" wird aber auch die Integrität der Anwendung positiv beeinflussen und die Konsistenz der Daten sicherstellen. Um das AutoCommit für eine Verbindung auszuschalten verwende man folgenden Code zum Aufbau dieser Verbindung: con = DriverManager.getConnection(url, userid, password); con.setAutoCommit(false); 1.2 Begrenzen der zu verarbeitenden "columns" SQL ist eine übersichtliche und mächtige Sprache. Eine Anweisung wie SELECT * FROM EMP_ACT holt alle Daten aus der Tabelle. Das Statement ist einfach zu formulieren, aber für DB2 hat es folgende Form: SELECT EMPNO, PROJNO, ACTNO, EMPTIME, EMSTDATE,EMENDATE FROM EMP_ACT Hier entsteht zusätzlicher "overhead" durch das lesen jeder "column" und der Modifikation der Query. Z.B. müssen "string values" von Unicode aus Java auf die EBCDIC/ASCII Zeichensets von DB2 konvertiert werden. Zusätzlich dazu erzeugt Java ein Objekt für jede Spalte, die einen anderen Datentyp verwendet, als die vorgegebenen "Java primitive character types" wie VARCHAR s. Gibt man mehr Spalten an, als man wirklich benötigt, so verschwendet man "resources" und wird die Performance negativ beeinflussen. Um eine bestmögliche Performance für SQL Statement zu erzielen, sollte man die zu lesenden Spalten auf ein benötigtes Minimum reduzieren. 1.3 Nutzung von "online checking" Mit dem Kommando db2sqljcustomize kann man SQLJ Profile erzeugen. Dieses Kommando kann aber auch über die -onlinecheck Option ( = YES) angestoßen werden. Dieses Kommando beinhaltet einen "online checker" für "static SQL". Der "online checker" durchläuft verschiedene Funktionen, u. a. die Prüfung auf die JDBC/SQLJKompatibilität und Konvertierungsverarbeitung. Er bestimmt die Länge der "string columns" und kann so die "run-time performance" der Java Applikation verbessern. Um diese Option nutzen zu können, muss man auf eine DB2 Datenbank verbunden sein, während man das db2sqljcustomize Kommando verwendet. 1.4 Tuning der "JVM heap size" Java Programme laufen auf der Java Virtual Machine(JVM). Jede JVM besitzt eine "default initial heap size" von 1 MB und eine "default maximum heap size" von 8 MB. Man sollte die "heap size" über die -ms ("starting heap size") und -mx ("maximum heap size") Parameter der java "command line parameter" setzen. "heap" wird aus einer Reihe von Performancegründen verwendet. Bei SQLJ oder JDBC wird ein Java Programm typischerweise damit beenden, dass es eine Menge von Java Objekten erzeugt und löscht. Diese Objekte sind zum Zugriff und zum Halten der relationalen Daten(objekte) notwendig. Dazu sei gesagt, dass die "default heap sizes" in der Regel nicht groß genug sind, um eine adäquate Performance zuzulassen. Eine Erhöhung dieser Werte kann zu einer besseren "run-time performance" für eine Applikation führen. Mit einer größeren "heap" hat man mehr Platz für das Erzeugen der Java Objekte. Außerdem kann das setzen von "minimum" und "maximum heap size" auf denselben wert die Performance insofern positiv beeinflussen als der Prozess der "reallocation" des Speichers für das "heap" beschleunigt wird. Man sollte darauf achten, dass größere "heap sizes" in weniger häufigen "garbage collection" Aktionen resultieren – "garbage collection" für größere "heaps" dauern länger. 1.5 Verwenden von CACHEDYN DB2 kann die Resultate eines Prepare eines "dynamic SQL" Statements in einem "cache"Speicher ablegen. Benutzt man JDBC Statements in einer Applikation oder werden in SQLJ spezielle Operationen, wie "cursor-controlled updates" auf einem Objekt außerhalb des Cursors ausgeführt, so wird die Applikation "dynamic SQL" ausführen. DB2 kann "dynamic SQL" Statements nur im Cache ablegen, wenn der Parameter CACHEDYN=YES in den Subsystemparametern gesetzt ist. Der Einsatz von "Statement caching" kann die Kosten von "dynamic SQL" näher an die Kosten des "static SQL" Verfahrens bringen. 2 Vermeiden allgemeiner Fehler 2.1 "Connection contexts" In diesem Kapitel werden einige der am weitesten verbreiteten Fehler, die die Performance von DB2 in Zusammenhang mit Java Applikationen beeinflussen, dargestellt. Wie bereits erwähnt, hat der SQLJ Code, der hier gezeigt wird einen "context parameter" nach der SQLJ Anweisung: #sql [context] {SELECT EMPNO INTO :strEmpNo FROM EMP_ACT WHERE PROJNO =:strProjNo}; Was ist der Sinn dieses optionalen "context Parameters"? Der "context" eines SQL Statements definiert die Besonderheiten einer "connection", die zum Zugriff auf die Datenbank gewünscht werden; z.B. "user name" und Zieldatenbank. Man kann also mehr als einen "context" in einer Applikation angeben. "Multiple contexts" betreffen beispielsweise unterschiedliche Benutzer – "uncommitted" Modifikationen, die für einen bestimmten "context" gedacht sind, sind für andere "contexts" nicht sichtbar und ein "rollback" oder "commit" für einen bestimmten "context" gilt ebenfalls nicht für andere "contexts" usw. Obwohl dieser Parameter optional ist, existiert mindestens ein "context" für jede "connection". Spezifiziert man den "context" für ein SQL Statement nicht selbst, so nutzt die Applikation einen "default context". Man muss also darauf achten, dass dies nicht ungewollt geschieht, nur weil man vergessen hat, dass jede Verbindung einen "context" zusammen mit dem jeweiligen SQL Statement mitliefert. Man sollte also möglichst keine "multiple contexts" zusammen mit JVM und den zugehörigen "database resources" nutzen. Sie könnten auch in einem "deadlock" zwischen zwei "connection contexts" derselben Applikation enden. 2.2 Der Nutzen von "DefaultContext" Eine Methode, einen ungeeigneten "default context" anzuwenden, ist die Definition eines eigenen DefaultContext Objekts in einer Applikation: DefaultContext ctx = DefaultContext.getDefaultContext(); if (ctx == null) { // Aufbau des URL (sample ist der Name der DB) String url = "jdbc:db2://localhost:50000/SAMPLE"; // verbinden mit der 'sample' Database mit user ID und password con = DriverManager.getConnection(url, "myusername", "mypassword"); con.setAutoCommit(false); ctx = new DefaultContext(con); DefaultContext.setDefaultContext(ctx); } ... Dieser Code prüft das Vorhandensein eines "default context" und weist dann die neu erstellte "connection" dem lokalen DefaultContext Objekt zu. 2.3 "Cleaning up" Keine Aufgabe ist erledigt bis der "cleanup"-Prozess beendet ist. Diese Maxime gilt auch für Java Code. Greift man auf DB2 zu werden eine Reihe unterschiedlicher Objekte genutzt. Nutzt man nun JDBC zum Datenzugriff, werden ResultSets erzeugt, die die Ergebnisse der Query enthalten. Man kann auch PreparedStatement und CallableStatement Objekte erzeugen, um SQL auszuführen. Man benutzt SQLJ Iteratoren, um "multiple rows" einer zurückgegebenen Datenmenge zu verarbeiten. Jedes dieser Objekte verwendet bestimmte Ressourcen sowohl im Speicher, als auch in JDBC. Man muss darauf achten, diese explizit wieder ferizugeben ("close") wenn man mit seiner Arbeit fertig ist und diese nicht mehr benötigt. Werden beispielsweise ResultSets nicht geschlossen, riskiert man einen Engpass in diesen Ressourcen. Der benutzte "database cursor" wird solange verwaltet bis das zugehörige PreparedStatement geschlossen wird. CallableStatements geben ihre "holds" in den "call sections" nicht frei bis sie geschlossen werden. Alle diese Objekte werden geschlossen wenn ihre zugehörige "connection" zur Datenbank geschlossen wird. Dennoch, das explizite Schließen dieser Objekte allein stellt nicht sicher, dass keine unnötigen Ressourcen mehr mitverwaltet werden. Die Vorgehensweise ist aber jedenfalls die bessere Programmiertechnik. Für SQLJ gilt, man kann einfach die close() Methode auf den "connection context" anwenden, um sowohl "context" als auch die unterliegende JDBC "connection" zu schließen. In JDBC aber kann man nicht einfach die unterliegende "connection", die mit der getConnection() Methode für den "context" zurückgeliefert wurde schließen: Man muss hier beides schließen – die "connection" und die "context objects".