ORA-01000, der Fehler mit maximal offenen Cursorn, ist ein äußerst häufiger Fehler bei der Entwicklung von Oracle-Datenbanken. Im Kontext von Java tritt dies auf, wenn die Anwendung versucht, mehr ResultSets zu öffnen, als auf einer Datenbankinstanz Cursor konfiguriert sind.
Häufige Ursachen sind:
Konfigurationsfehler
- Ihre Anwendung hat mehr Threads, die die Datenbank abfragen, als Cursor in der Datenbank. Ein Fall ist, wenn Sie einen Verbindungs- und Thread-Pool haben, der größer ist als die Anzahl der Cursor in der Datenbank.
- Sie haben viele Entwickler oder Anwendungen, die mit derselben DB-Instanz verbunden sind (die wahrscheinlich viele Schemas enthalten wird), und zusammen verwenden Sie zu viele Verbindungen.
Lösung:
Cursorleck
- Die Anwendungen schließen weder ResultSets (in JDBC) noch Cursor (in gespeicherten Prozeduren in der Datenbank).
- Lösung : Cursorlecks sind Fehler. Das Erhöhen der Anzahl der Cursor in der Datenbank verzögert lediglich den unvermeidlichen Fehler. Lecks können mithilfe statischer Code-Analyse , 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 zu "Beseitigung von Lecks" gehen.
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 Leser in einem ResultSet befindet. Jede SELECT-Anweisung verfügt über einen Cursor, und gespeicherte PL / SQL-Prozeduren können so viele Cursor öffnen und verwenden, wie sie benötigen. Auf Orafaq erfahren Sie mehr über Cursor .
Eine Datenbankinstanz bedient normalerweise mehrere verschiedene Schemas , viele verschiedene Benutzer mit jeweils mehreren Sitzungen . Zu diesem Zweck steht eine feste Anzahl von Cursorn für alle Schemas, Benutzer und Sitzungen zur Verfügung. Wenn alle Cursor geöffnet sind (verwendet werden) und eine Anforderung eingeht, für die ein neuer Cursor erforderlich ist, schlägt die Anforderung mit einem ORA-010000-Fehler fehl.
Suchen und Einstellen der Anzahl der Cursor
Die Nummer wird normalerweise vom DBA bei der Installation konfiguriert. Auf die Anzahl der derzeit verwendeten Cursor, die maximale Anzahl und die Konfiguration kann in den Administratorfunktionen in Oracle SQL Developer zugegriffen werden . In SQL kann Folgendes festgelegt werden:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Verknüpfen von JDBC in der JVM mit Cursorn in der Datenbank
Die folgenden JDBC-Objekte sind eng mit den folgenden Datenbankkonzepten verbunden:
- JDBC - Verbindung ist die Client - Darstellung einer Datenbank - Sitzung und bietet Datenbanktransaktionen . Bei einer Verbindung kann jeweils nur eine Transaktion geöffnet sein (Transaktionen können jedoch verschachtelt sein).
- Ein JDBC ResultSet wird von einem einzelnen Cursor in der Datenbank unterstützt. Wenn close () im ResultSet aufgerufen wird, wird der Cursor losgelassen.
- Ein JDBC CallableStatement ruft eine gespeicherte Prozedur in der Datenbank auf, die häufig in PL / SQL geschrieben ist. Die gespeicherte Prozedur kann null oder mehr Cursor erstellen und einen Cursor als JDBC ResultSet zurückgeben.
JDBC ist threadsicher: 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 wichtige Einschränkung besteht darin, dass nicht mehr als ein ResultSet gleichzeitig für ein einzelnes PreparedStatement geöffnet sein kann. Siehe Unterstützt Oracle DB mehrere (parallele) Vorgänge pro Verbindung?
Beachten Sie, dass ein Datenbank-Commit für eine Verbindung erfolgt und daher alle DMLs (INSERT, UPDATE und DELETE) für diese Verbindung zusammen festgeschrieben werden. Wenn Sie mehrere Transaktionen gleichzeitig unterstützen möchten, müssen Sie daher für jede gleichzeitige Transaktion mindestens eine Verbindung haben.
JDBC-Objekte schließen
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 durch close () ausgelöste Ausnahme ignoriert:
- Wenn Sie das ResultSet einfach ohne try {} catch {} schließen, schlägt dies möglicherweise fehl und verhindert, dass die Anweisung geschlossen wird
- Wir möchten zulassen, dass jede Ausnahme, die im Hauptteil des Versuchs ausgelöst wird, an den Anrufer weitergegeben wird. Wenn Sie eine Schleife haben, z. B. Anweisungen erstellen und ausführen, denken Sie daran, jede Anweisung innerhalb der Schleife zu schließen.
In Java 7 hat Oracle die AutoCloseable-Oberfläche eingeführt, die den größten Teil der Java 6-Boilerplate durch einen schönen syntaktischen Zucker ersetzt.
JDBC-Objekte halten
JDBC-Objekte können sicher in lokalen Variablen, Objektinstanzen und Klassenmitgliedern gespeichert werden. Es ist im Allgemeinen besser zu praktizieren:
- Verwenden Sie Objektinstanz- oder Klassenmitglieder, um JDBC-Objekte zu speichern, die über einen längeren Zeitraum mehrmals 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 strengen Threading-Modell folgen:
- Nur der Anwendungsserver erstellt Threads (mit denen er eingehende Anforderungen verarbeitet).
- Nur der Anwendungsserver erstellt Verbindungen (die Sie aus dem Verbindungspool erhalten).
- Beim Speichern von Werten (Status) zwischen Aufrufen müssen Sie sehr vorsichtig sein. Speichern Sie niemals Werte in Ihren eigenen Caches oder statischen Mitgliedern - dies ist in Clustern und anderen seltsamen Bedingungen nicht sicher, und der Anwendungsserver kann schreckliche Daten an Ihren Daten anrichten. Verwenden Sie stattdessen Stateful Beans oder eine Datenbank.
- Insbesondere nie halten JDBC - Objekte (Verbindungen, Result, PreparedStatements, usw.) über verschiedene Remote - Anrufungen - lassen Sie den Application Server diese verwalten. Der Anwendungsserver stellt nicht nur einen Verbindungspool bereit, sondern speichert auch Ihre PreparedStatements zwischen.
Leckagen beseitigen
Es gibt eine Reihe von Prozessen und Tools, mit denen JDBC-Lecks erkannt und beseitigt werden können:
Während der Entwicklung ist das frühzeitige Erkennen von Fehlern bei weitem der beste Ansatz:
Entwicklungspraktiken: Gute Entwicklungspraktiken sollten die Anzahl der Fehler in Ihrer Software verringern, bevor sie den Entwickler-Desk verlässt. Spezifische Praktiken umfassen:
- Paarprogrammierung , um diejenigen ohne ausreichende Erfahrung auszubilden
- Codeüberprüfungen, weil viele Augen besser als eines sind
- Unit-Tests , dh Sie können Ihre gesamte Codebasis mit einem Testwerkzeug trainieren, wodurch die Reproduktion von Lecks trivial wird
- Verwenden Sie vorhandene Bibliotheken für das Verbindungspooling, anstatt Ihre eigenen zu erstellen
Statische Code-Analyse: Verwenden Sie ein Tool wie die hervorragenden Findbugs , um eine statische Code-Analyse durchzuführen. Dies nimmt viele Stellen auf, an denen close () nicht korrekt behandelt wurde. Findbugs hat ein Plugin für Eclipse, läuft aber auch eigenständig für Einzelstücke und ist in Jenkins CI und andere Build-Tools integriert
Zur Laufzeit:
Haltbarkeit und Engagement
- Wenn die ResultSet-Haltbarkeit ResultSet.CLOSE_CURSORS_OVER_COMMIT lautet, wird das ResultSet geschlossen, wenn die Connection.commit () -Methode aufgerufen wird. Dies kann mit Connection.setHoldability () oder mit der überladenen Connection.createStatement () -Methode festgelegt werden.
Protokollierung zur Laufzeit.
- Fügen Sie gute Protokollanweisungen in Ihren Code ein. Diese sollten klar und verständlich sein, damit der Kunde, die Support-Mitarbeiter und die Teamkollegen ohne Schulung verstehen können. Sie sollten knapp sein und das Drucken der Status- / internen Werte von Schlüsselvariablen und Attributen umfassen, damit Sie die Verarbeitungslogik verfolgen können. Eine gute Protokollierung ist für das Debuggen von Anwendungen von grundlegender Bedeutung, insbesondere von Anwendungen, die bereitgestellt wurden.
Sie können Ihrem Projekt einen JDBC-Debugging-Treiber hinzufügen (zum Debuggen - stellen Sie ihn nicht bereit). Ein Beispiel (ich habe es nicht verwendet) ist log4jdbc . Anschließend müssen Sie eine einfache Analyse dieser Datei durchführen, um festzustellen, welche Ausführungen keinen entsprechenden Abschluss haben. Das Zählen des Öffnens und Schließens sollte hervorheben, ob ein potenzielles Problem vorliegt
- Überwachung der Datenbank. Überwachen Sie Ihre laufende Anwendung mit Tools wie der SQL Developer-Funktion 'SQL überwachen' oder Quest's TOAD . 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 ein Leck mit dieser SQL haben. Suchen Sie Ihren Code und überprüfen Sie.
Andere Gedanken
Können Sie WeakReferences verwenden, um das Schließen von Verbindungen zu handhaben?
Schwache und weiche Referenzen ermöglichen es Ihnen, ein Objekt so zu referenzieren, dass die JVM den Referenten jederzeit mit Müll sammeln kann, wenn dies für angemessen erachtet wird (vorausgesetzt, es gibt keine starken Referenzketten für dieses Objekt).
Wenn Sie eine ReferenceQueue im Konstruktor an die weiche oder schwache Referenz übergeben, wird das Objekt in die ReferenceQueue gestellt, wenn das Objekt bei seinem Auftreten GC-geprüft wird (falls es überhaupt auftritt). 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 Steuerung der Finalisierung, aber Sie können niemals einen Verweis auf das ursprüngliche Objekt erhalten, daher wird es schwierig sein, die close () -Methode darauf aufzurufen.
Es ist jedoch selten eine gute Idee, zu versuchen, zu steuern, wann der GC ausgeführt wird (Schwache, weiche und Phantomreferenzen lassen Sie wissen, nachdem das Objekt für den GC in die Warteschlange gestellt wurde). Wenn der Speicher in der JVM groß ist (z. B. -Xmx2000m), wird das Objekt möglicherweise nie GC-fähig, und der ORA-01000 wird weiterhin angezeigt. Wenn der JVM-Speicher im Verhältnis zu den Anforderungen Ihres Programms klein ist, werden die Objekte ResultSet und PreparedStatement möglicherweise sofort nach der Erstellung (bevor Sie daraus lesen können) überprüft, was wahrscheinlich zu einem Fehler Ihres Programms führen wird.
TL; DR: Der schwache Referenzmechanismus ist keine gute Möglichkeit, Statement- und ResultSet-Objekte zu verwalten und zu schließen.
for (String language : additionalLangs) {