ORA-01000, de maximum-open-cursors-fout, is een veelvoorkomende fout bij de ontwikkeling van Oracle-databases. In de context van Java gebeurt het wanneer de toepassing meer ResultSets probeert te openen dan er geconfigureerde cursors op een database-instantie zijn.
Veelvoorkomende oorzaken zijn:
-
Configuratiefout
- Je hebt meer threads in je applicatie die de database doorzoeken dan cursors op de DB. Eén geval is waar u een verbinding en threadpool hebt die groter is dan het aantal cursors in de database.
- U hebt veel ontwikkelaars of toepassingen die zijn aangesloten op dezelfde DB-instantie (die waarschijnlijk veel schema's zal bevatten) en samen gebruikt u te veel verbindingen.
-
Oplossing:
- Het aantal cursors in de database verhogen (als de bronnen dit toelaten) of
- Het aantal threads in de applicatie verminderen.
-
Cursorlek
- De toepassing sluit ResultSets (in JDBC) of cursors (in opgeslagen procedures in de database) niet af
- Oplossing :Cursorlekken zijn bugs; het verhogen van het aantal cursors op de DB vertraagt gewoon de onvermijdelijke mislukking. Lekken kunnen worden gevonden met behulp van statische code-analyse, JDBC of logboekregistratie op applicatieniveau en databasemonitoring.
Achtergrond
Dit gedeelte beschrijft een deel van de theorie achter cursors en hoe JDBC moet worden gebruikt. Als u de achtergrond niet hoeft te weten, kunt u dit overslaan en direct naar 'Elimination Leaks' gaan.
Wat is een cursor?
Een cursor is een bron in de database die de status van een query bevat, met name de positie waar een lezer zich in een ResultSet bevindt. Elke SELECT-instructie heeft een cursor en in PL/SQL opgeslagen procedures kunnen zoveel cursors openen en gebruiken als nodig is. U kunt meer te weten komen over cursors op Orafaq.
Een database-instantie bedient doorgaans verschillende schema's , veel verschillende gebruikers elk met meerdere sessies . Hiervoor heeft het een vast aantal cursors beschikbaar voor alle schema's, gebruikers en sessies. Wanneer alle cursors open zijn (in gebruik) en er een verzoek binnenkomt waarvoor een nieuwe cursor nodig is, mislukt het verzoek met een ORA-010000-fout.
Het aantal cursors zoeken en instellen
Het nummer wordt normaal gesproken geconfigureerd door de DBA bij de installatie. Het aantal cursors dat momenteel in gebruik is, het maximale aantal en de configuratie zijn toegankelijk in de beheerdersfuncties in Oracle SQL Developer. Vanuit SQL kan het worden ingesteld met:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
JDBC in de JVM relateren aan cursors op de DB
De onderstaande JDBC-objecten zijn nauw gekoppeld aan de volgende databaseconcepten:
- JDBC Verbinding is de klantrepresentatie van een database sessie en biedt database transacties . Een verbinding kan slechts één enkele transactie tegelijk open hebben (maar transacties kunnen worden genest)
- Een JDBC ResultSet wordt ondersteund door een enkele cursor op de databank. Wanneer close() wordt aangeroepen op de ResultSet, wordt de cursor losgelaten.
- Een JDBC CallableStatement roept een opgeslagen procedure op op de database, vaak geschreven in PL/SQL. De opgeslagen procedure kan nul of meer cursors maken en kan een cursor retourneren als een JDBC ResultSet.
JDBC is thread-safe:het is prima om de verschillende JDBC-objecten tussen threads door te geven.
U kunt de verbinding bijvoorbeeld in één thread maken; een andere thread kan deze verbinding gebruiken om een PreparedStatement te maken en een derde thread kan de resultatenset verwerken. De enige grote beperking is dat u niet meer dan één ResultSet tegelijk kunt openen op één PreparedStatement. Zie Ondersteunt Oracle DB meerdere (parallelle) bewerkingen per verbinding?
Merk op dat een database-commit plaatsvindt op een verbinding, en dus zullen alle DML (INSERT, UPDATE en DELETE's) op die verbinding samen committen. Als u dus meerdere transacties tegelijk wilt ondersteunen, moet u voor elke gelijktijdige transactie minimaal één verbinding hebben.
JDBC-objecten sluiten
Een typisch voorbeeld van het uitvoeren van een ResultSet is:
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) { }
}
Merk op hoe de clausule finaal elke uitzondering negeert die wordt veroorzaakt door de close():
- Als u de ResultSet gewoon sluit zonder de try {} catch {}, kan het mislukken en wordt voorkomen dat de verklaring wordt gesloten
- We willen toestaan dat elke uitzondering in de hoofdtekst van de poging wordt doorgegeven aan de beller. Als je een lus hebt over, bijvoorbeeld, het maken en uitvoeren van Statements, vergeet dan niet elke Statement binnen de lus te sluiten.
In Java 7 heeft Oracle de AutoCloseable-interface geïntroduceerd die het grootste deel van de Java 6-boilerplate vervangt door een aantal mooie syntactische suikers.
JDBC-objecten vasthouden
JDBC-objecten kunnen veilig worden vastgehouden in lokale variabelen, objectinstanties en klassenleden. Het is over het algemeen beter om:
- Gebruik objectinstantie of klasseleden om JDBC-objecten te bewaren die meerdere keren gedurende een langere periode worden hergebruikt, zoals Connections en PreparedStatements
- Gebruik lokale variabelen voor ResultSets, aangezien deze worden verkregen, doorgelust en vervolgens worden gesloten binnen het bereik van een enkele functie.
Er is echter één uitzondering:als u EJB's of een Servlet/JSP-container gebruikt, moet u een strikt threading-model volgen:
- Alleen de toepassingsserver maakt threads aan (waarmee het inkomende verzoeken afhandelt)
- Alleen de toepassingsserver maakt verbindingen (die u verkrijgt uit de verbindingspool)
- Bij het opslaan van waarden (status) tussen aanroepen, moet je heel voorzichtig zijn. Sla nooit waarden op in uw eigen caches of statische leden - dit is niet veilig in clusters en andere vreemde omstandigheden, en de toepassingsserver kan vreselijke dingen met uw gegevens doen. Gebruik in plaats daarvan stateful beans of een database.
- In het bijzonder nooit houd JDBC-objecten (Connections, ResultSets, PreparedStatements, enz.) over verschillende externe aanroepen - laat de toepassingsserver dit beheren. De toepassingsserver biedt niet alleen een verbindingspool, maar slaat ook uw PreparedStatements op in de cache.
Lekken opheffen
Er zijn een aantal processen en tools beschikbaar om JDBC-lekken te helpen detecteren en elimineren:
-
Tijdens de ontwikkeling - het vroegtijdig opsporen van bugs is verreweg de beste aanpak:
-
Ontwikkelingspraktijken:goede ontwikkelingspraktijken zouden het aantal bugs in uw software moeten verminderen voordat deze het bureau van de ontwikkelaar verlaat. Specifieke praktijken zijn onder meer:
- Programmeren in paren, om mensen zonder voldoende ervaring op te leiden
- Coderecensies omdat veel ogen beter zijn dan één
- Eenheidstests, wat betekent dat u al uw codebasis kunt oefenen met een testtool die het reproduceren van lekken triviaal maakt
- Gebruik bestaande bibliotheken voor het poolen van verbindingen in plaats van uw eigen bibliotheken te bouwen
-
Statische code-analyse:gebruik een tool zoals het uitstekende Findbugs om een statische code-analyse uit te voeren. Dit pikt veel plaatsen op waar close() niet correct is afgehandeld. Findbugs heeft een plug-in voor Eclipse, maar het werkt ook stand-alone voor eenmalige toepassingen, heeft integraties in Jenkins CI en andere build-tools
-
-
Tijdens runtime:
-
Houdbaarheid en commitment
- Als de houdbaarheid van de ResultSet ResultSet.CLOSE_CURSORS_OVER_COMMIT is, wordt de ResultSet gesloten wanneer de methode Connection.commit() wordt aangeroepen. Dit kan worden ingesteld met Connection.setHoldability() of door de overbelaste methode Connection.createStatement() te gebruiken.
-
Loggen tijdens runtime.
- Plaats goede log-statements in je code. Deze moeten duidelijk en begrijpelijk zijn, zodat de klant, het ondersteunend personeel en teamgenoten het zonder training kunnen begrijpen. Ze moeten beknopt zijn en het afdrukken van de status/interne waarden van belangrijke variabelen en attributen omvatten, zodat u de verwerkingslogica kunt traceren. Een goede logboekregistratie is van fundamenteel belang voor het debuggen van toepassingen, vooral die welke zijn geïmplementeerd.
-
U kunt een JDBC-stuurprogramma voor foutopsporing aan uw project toevoegen (voor foutopsporing - gebruik het niet echt). Een voorbeeld (ik heb het niet gebruikt) is log4jdbc. U moet dan een eenvoudige analyse van dit bestand uitvoeren om te zien welke uitvoeringen geen overeenkomstige afsluiting hebben. Het tellen van het openen en sluiten zou moeten aangeven of er een mogelijk probleem is
- Bewaken van de database. Bewaak uw actieve applicatie met behulp van de tools zoals de SQL Developer 'Monitor SQL'-functie of Quest's TOAD. Monitoring wordt beschreven in dit artikel. Tijdens het monitoren ondervraag je de geopende cursors (bijv. uit tabel v$sesstat) en bekijk je hun SQL. Als het aantal cursors toeneemt en (het allerbelangrijkste) wordt gedomineerd door één identieke SQL-instructie, weet je dat je een lek hebt met die SQL. Zoek uw code en bekijk.
-
Andere gedachten
Kun je WeakReferences gebruiken om afsluitende verbindingen af te handelen?
Zwakke en zachte referenties zijn manieren om u in staat te stellen naar een object te verwijzen op een manier die de JVM in staat stelt de referent op elk gewenst moment te verzamelen (ervan uitgaande dat er geen sterke referentieketens zijn naar dat object).
Als u een ReferenceQueue in de constructor doorgeeft aan de zachte of zwakke Reference, wordt het object in de ReferenceQueue geplaatst wanneer het object wordt GC'ed wanneer het zich voordoet (als het al voorkomt). Met deze aanpak kunt u communiceren met de voltooiing van het object en kunt u het object op dat moment sluiten of afronden.
Phantom-referenties zijn een beetje vreemder; hun doel is alleen om de afronding te regelen, maar je kunt nooit een verwijzing naar het originele object krijgen, dus het zal moeilijk zijn om de methode close() erop aan te roepen.
Het is echter zelden een goed idee om te proberen te controleren wanneer de GC wordt uitgevoerd (Weak, Soft en PhantomReferences laten u achteraf weten dat het object in de wachtrij staat voor GC). Als de hoeveelheid geheugen in de JVM groot is (bijv. -Xmx2000m), is het zelfs mogelijk dat u nooit GC het object, en je zult nog steeds de ORA-01000 ervaren. Als het JVM-geheugen klein is in verhouding tot de vereisten van uw programma, kan het zijn dat de ResultSet- en PreparedStatement-objecten onmiddellijk na het maken worden geGCed (voordat u ervan kunt lezen), waardoor uw programma waarschijnlijk zal mislukken.
TL;DR: Het zwakke referentiemechanisme is geen goede manier om Statement- en ResultSet-objecten te beheren en te sluiten.