ORA-01000, der Maximum-Open-Cursors-Fehler, ist ein extrem häufiger Fehler in der Oracle-Datenbankentwicklung. Im Kontext von Java geschieht dies, wenn die Anwendung versucht, mehr ResultSets zu öffnen, als Cursor auf einer Datenbankinstanz konfiguriert sind.
Häufige Ursachen sind:
-
Konfigurationsfehler
- Sie haben mehr Threads in Ihrer Anwendung, die die Datenbank abfragen, als Cursor auf der Datenbank. In einem Fall haben Sie eine Verbindung und einen Thread-Pool, der größer ist als die Anzahl der Cursor in der Datenbank.
- Sie haben viele Entwickler oder Anwendungen, die mit derselben DB-Instance verbunden sind (die wahrscheinlich viele Schemata enthalten wird), und zusammen verwenden Sie zu viele Verbindungen.
-
Lösung:
- Erhöhen der Anzahl der Cursor auf der Datenbank (wenn die Ressourcen dies zulassen) oder
- Verringern der Anzahl von Threads in der Anwendung.
-
Cursorleck
- Die Anwendung schließt keine Ergebnismengen (in JDBC) oder Cursor (in gespeicherten Prozeduren in der Datenbank)
- Lösung :Cursorlecks sind Fehler; Eine Erhöhung der Anzahl von Cursorn in der Datenbank verzögert lediglich den unvermeidlichen Ausfall. Lecks können mithilfe von statischer Codeanalyse, JDBC- oder Protokollierung auf Anwendungsebene und Datenbanküberwachung gefunden werden.
Hintergrund
Dieser Abschnitt beschreibt einige der Theorien hinter Cursorn und wie JDBC verwendet werden sollte. Wenn Sie den Hintergrund nicht kennen müssen, können Sie dies überspringen und direkt mit „Lecks beseitigen“ fortfahren.
Was ist ein Cursor?
Ein Cursor ist eine Ressource in der Datenbank, die den Status einer Abfrage enthält, insbesondere die Position, an der sich ein Reader in einem ResultSet befindet. Jede SELECT-Anweisung hat einen Cursor, und gespeicherte PL/SQL-Prozeduren können beliebig viele Cursor öffnen und verwenden. Weitere Informationen zu Cursors finden Sie auf Orafaq.
Eine Datenbankinstanz dient normalerweise mehreren verschiedenen Schemata , viele verschiedene Benutzer jeweils mit mehreren Sitzungen . Dazu steht ihm für alle Schemas, Benutzer und Sessions eine feste Anzahl von Cursorn zur Verfügung. Wenn alle Cursor geöffnet (in Verwendung) sind und eine Anfrage eingeht, die einen neuen Cursor erfordert, schlägt die Anfrage mit einem ORA-010000-Fehler fehl.
Ermitteln und Einstellen der Anzahl der Cursors
Die Nummer wird normalerweise vom DBA bei der Installation konfiguriert. Die Anzahl der aktuell verwendeten Cursor, die maximale Anzahl und die Konfiguration können in den Administratorfunktionen in Oracle SQL Developer abgerufen werden. Von SQL kann es gesetzt werden mit:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Verknüpfung von JDBC in der JVM mit Cursorn in der DB
Die folgenden JDBC-Objekte sind eng an die folgenden Datenbankkonzepte gekoppelt:
- JDBC-Verbindung ist die Client-Darstellung einer Sitzung einer Datenbank und stellt Datenbank-Transaktionen bereit . Eine Verbindung kann immer nur eine einzige Transaktion geöffnet haben (aber Transaktionen können verschachtelt sein)
- Ein JDBC-ResultSet wird von einem einzigen Cursor unterstützt auf der Datenbank. Wenn close() für das ResultSet aufgerufen wird, wird der Cursor losgelassen.
- Ein JDBC CallableStatement ruft eine gespeicherte Prozedur auf auf der Datenbank, oft in PL/SQL geschrieben. Die gespeicherte Prozedur kann null oder mehr Cursor erstellen und einen Cursor als JDBC-ResultSet zurückgeben.
JDBC ist Thread-sicher:Es ist völlig in Ordnung, die verschiedenen JDBC-Objekte zwischen Threads zu übergeben.
Beispielsweise können Sie die Verbindung in einem Thread erstellen; ein anderer Thread kann diese Verbindung verwenden, um ein PreparedStatement zu erstellen, und ein dritter Thread kann die Ergebnismenge verarbeiten. Die einzige große Einschränkung besteht darin, dass Sie zu keinem Zeitpunkt mehr als ein ResultSet auf einem einzelnen PreparedStatement geöffnet haben können. Siehe Unterstützt Oracle DB mehrere (parallele) Operationen pro Verbindung?
Beachten Sie, dass ein Datenbank-Commit auf einer Verbindung stattfindet und daher alle DML (INSERT, UPDATE und DELETE) auf dieser Verbindung zusammen festgeschrieben werden. Wenn Sie also mehrere Transaktionen gleichzeitig unterstützen möchten, müssen Sie mindestens eine Verbindung für jede gleichzeitige Transaktion haben.
Schließen von JDBC-Objekten
Ein typisches Beispiel für die Ausführung eines ResultSet ist:
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery( "SELECT FULL_NAME FROM EMP" );
try {
while ( rs.next() ) {
System.out.println( "Name: " + rs.getString("FULL_NAME") );
}
} finally {
try { rs.close(); } catch (Exception ignore) { }
}
} finally {
try { stmt.close(); } catch (Exception ignore) { }
}
Beachten Sie, wie die finally-Klausel jede Ausnahme ignoriert, die von close():
ausgelöst wird- Wenn Sie das ResultSet einfach ohne try {} catch {} schließen, kann es fehlschlagen und verhindern, dass die Anweisung geschlossen wird
- Wir möchten zulassen, dass jede Ausnahme, die im Hauptteil des Versuchs ausgelöst wird, an den Aufrufer weitergegeben wird. Wenn Sie beispielsweise eine Schleife zum Erstellen und Ausführen von Anweisungen haben, denken Sie daran, jede Anweisung innerhalb der Schleife zu schließen.
In Java 7 hat Oracle die AutoCloseable-Schnittstelle eingeführt, die den größten Teil der Java-6-Boilerplate durch etwas netten syntaktischen Zucker ersetzt.
Halten von JDBC-Objekten
JDBC-Objekte können sicher in lokalen Variablen, Objektinstanzen und Klassenmitgliedern gespeichert werden. Es ist im Allgemeinen besser, Folgendes zu tun:
- Verwenden Sie Objektinstanzen oder Klassenmitglieder, um JDBC-Objekte zu speichern, die mehrere Male über einen längeren Zeitraum wiederverwendet werden, z. B. Connections und PreparedStatements
- Verwenden Sie lokale Variablen für ResultSets, da diese normalerweise im Rahmen einer einzelnen Funktion abgerufen, durchlaufen und dann geschlossen werden.
Es gibt jedoch eine Ausnahme:Wenn Sie EJBs oder einen Servlet/JSP-Container verwenden, müssen Sie einem strikten Threading-Modell folgen:
- Nur der Anwendungsserver erstellt Threads (mit denen er eingehende Anfragen bearbeitet)
- Nur der Anwendungsserver erstellt Verbindungen (die Sie aus dem Verbindungspool beziehen)
- Beim Speichern von Werten (Status) zwischen Aufrufen müssen Sie sehr vorsichtig sein. Speichern Sie niemals Werte in Ihren eigenen Caches oder statischen Membern – dies ist über Cluster und andere seltsame Bedingungen hinweg nicht sicher, und der Anwendungsserver kann Ihren Daten schreckliche Dinge antun. Verwenden Sie stattdessen Stateful Beans oder eine Datenbank.
- Insbesondere nie Halten Sie JDBC-Objekte (Connections, ResultSets, PreparedStatements usw.) über verschiedene Remote-Aufrufe – überlassen Sie dies dem Anwendungsserver. Der Anwendungsserver stellt nicht nur einen Verbindungspool bereit, sondern speichert auch Ihre PreparedStatements.
Lecks beseitigen
Es gibt eine Reihe von Prozessen und Tools, mit denen Sie JDBC-Leaks erkennen und beseitigen können:
-
Während der Entwicklung - Fehler frühzeitig zu finden ist bei weitem der beste Ansatz:
-
Entwicklungspraktiken:Gute Entwicklungspraktiken sollten die Anzahl der Fehler in Ihrer Software reduzieren, bevor sie den Schreibtisch des Entwicklers verlässt. Zu den spezifischen Praktiken gehören:
- Paarprogrammierung, um diejenigen ohne ausreichende Erfahrung zu schulen
- Code-Reviews, weil viele Augen besser sind als eines
- Einheitentests, was bedeutet, dass Sie Ihre gesamte Codebasis von einem Testtool aus testen können, wodurch das Reproduzieren von Leaks trivial wird
- Verwenden Sie vorhandene Bibliotheken für das Verbindungspooling, anstatt Ihre eigenen zu erstellen
-
Statische Codeanalyse:Verwenden Sie ein Tool wie das hervorragende Findbugs, um eine statische Codeanalyse durchzuführen. Dies greift viele Stellen auf, an denen close() nicht korrekt gehandhabt wurde. Findbugs hat ein Plugin für Eclipse, aber es läuft auch eigenständig für einmalige Aktionen, hat Integrationen in Jenkins CI und andere Build-Tools
-
-
Zur Laufzeit:
-
Haltbarkeit und Commitment
- Wenn die ResultSet-Holdability ResultSet.CLOSE_CURSORS_OVER_COMMIT ist, dann wird das ResultSet geschlossen, wenn die Methode Connection.commit() aufgerufen wird. Dies kann mit Connection.setHoldability() oder mit der überladenen Methode Connection.createStatement() festgelegt werden.
-
Protokollierung zur Laufzeit.
- Fügen Sie gute Protokollanweisungen in Ihren Code ein. Diese sollten klar und verständlich sein, damit der Kunde, Supportmitarbeiter und Teamkollegen sie ohne Schulung verstehen können. Sie sollten knapp gehalten sein und den Status/die internen Werte von Schlüsselvariablen und Attributen enthalten, damit Sie die Verarbeitungslogik verfolgen können. Eine gute Protokollierung ist für das Debuggen von Anwendungen von grundlegender Bedeutung, insbesondere für diejenigen, die bereitgestellt wurden.
-
Sie können Ihrem Projekt einen Debugging-JDBC-Treiber hinzufügen (zum Debuggen – stellen Sie ihn nicht wirklich bereit). Ein Beispiel (ich habe es nicht verwendet) ist log4jdbc. Sie müssen dann eine einfache Analyse dieser Datei durchführen, um zu sehen, welche Ausführungen kein entsprechendes Schließen haben. Das Zählen der Öffnungen und Schließungen sollte hervorheben, ob es ein potenzielles Problem gibt
- Überwachung der Datenbank. Überwachen Sie Ihre laufende Anwendung mit Tools wie der SQL Developer-Funktion „Monitor SQL“ oder TOAD von Quest. Die Überwachung wird in diesem Artikel beschrieben. Während der Überwachung fragen Sie die geöffneten Cursor ab (z. B. aus der Tabelle v$sesstat) und überprüfen deren SQL. Wenn die Anzahl der Cursor zunimmt und (am wichtigsten) von einer identischen SQL-Anweisung dominiert wird, wissen Sie, dass Sie mit dieser SQL ein Leck haben. Durchsuchen Sie Ihren Code und überprüfen Sie ihn.
-
Andere Gedanken
Können Sie WeakReferences verwenden, um das Schließen von Verbindungen zu handhaben?
Schwache und weiche Referenzen sind Möglichkeiten, um es Ihnen zu ermöglichen, auf ein Objekt in einer Weise zu verweisen, die es der JVM ermöglicht, die Referenz zu jeder Zeit, die sie für geeignet hält, in den Garbage Collection zu sammeln (vorausgesetzt, es gibt keine starken Referenzketten zu diesem Objekt).
Wenn Sie eine ReferenceQueue im Konstruktor an die weiche oder schwache Referenz übergeben, wird das Objekt in der ReferenceQueue platziert, wenn das Objekt bei seinem Auftreten (falls es überhaupt auftritt) mit GC versehen wird. Mit diesem Ansatz können Sie mit der Finalisierung des Objekts interagieren und das Objekt in diesem Moment schließen oder finalisieren.
Phantomreferenzen sind etwas seltsamer; Ihr Zweck ist nur, die Finalisierung zu steuern, aber Sie können niemals eine Referenz auf das ursprüngliche Objekt erhalten, daher wird es schwierig sein, die Methode close() dafür aufzurufen.
Es ist jedoch selten eine gute Idee zu versuchen, zu kontrollieren, wann die GC ausgeführt wird (Weak, Soft und PhantomReferences lassen Sie im Nachhinein wissen dass das Objekt für GC eingereiht wird). Wenn die Menge an Arbeitsspeicher in der JVM groß ist (z. B. -Xmx2000m), könnten Sie dies sogar nie tun GC das Objekt, und Sie werden immer noch ORA-01000 erleben. Wenn der JVM-Speicher relativ zu den Anforderungen Ihres Programms klein ist, stellen Sie möglicherweise fest, dass die ResultSet- und PreparedStatement-Objekte unmittelbar nach der Erstellung GCed werden (bevor Sie daraus lesen können), was Ihr Programm wahrscheinlich zum Scheitern bringen wird.
TL;DR: Der schwache Referenzmechanismus ist keine gute Möglichkeit, Statement- und ResultSet-Objekte zu verwalten und zu schließen.