ORA-01000, l'errore massimo-cursori aperti, è un errore estremamente comune nello sviluppo di database Oracle. Nel contesto di Java, accade quando l'applicazione tenta di aprire più ResultSet di quanti siano i cursori configurati su un'istanza di database.
Le cause comuni sono:
-
Errore di configurazione
- Hai più thread nella tua applicazione che interrogano il database rispetto ai cursori sul DB. Un caso è quello in cui hai una connessione e un pool di thread maggiore del numero di cursori sul database.
- Hai molti sviluppatori o applicazioni collegati alla stessa istanza database (che probabilmente includerà molti schemi) e insieme stai utilizzando troppe connessioni.
-
Soluzione:
- Aumento del numero di cursori sul database (se le risorse lo consentono) oppure
- Diminuzione del numero di thread nell'applicazione.
-
Perdita del cursore
- Le applicazioni non stanno chiudendo ResultSet (in JDBC) o cursori (nelle procedure memorizzate sul database)
- Soluzione :Le perdite di cursore sono bug; l'aumento del numero di cursori sul DB ritarda semplicemente l'inevitabile fallimento. Le perdite possono essere rilevate utilizzando l'analisi del codice statico, la registrazione JDBC o a livello di applicazione e il monitoraggio del database.
Sfondo
Questa sezione descrive parte della teoria alla base dei cursori e come dovrebbe essere utilizzato JDBC. Se non hai bisogno di conoscere lo sfondo, puoi saltare questo passaggio e andare direttamente a "Eliminare le perdite".
Cos'è un cursore?
Un cursore è una risorsa nel database che contiene lo stato di una query, in particolare la posizione in cui si trova un lettore in un ResultSet. Ogni istruzione SELECT ha un cursore e le stored procedure PL/SQL possono aprire e utilizzare tutti i cursori necessari. Puoi saperne di più sui cursori su Orafaq.
Un'istanza di database in genere serve diversi schemi diversi , molti utenti diversi ciascuno con sessioni multiple . Per fare ciò, dispone di un numero fisso di cursori disponibili per tutti gli schemi, gli utenti e le sessioni. Quando tutti i cursori sono aperti (in uso) e arriva una richiesta che richiede un nuovo cursore, la richiesta non riesce con un errore ORA-010000.
Trovare e impostare il numero di cursori
Il numero è normalmente configurato dal DBA al momento dell'installazione. Il numero di cursori attualmente in uso, il numero massimo e la configurazione sono accessibili nelle funzioni di amministratore in Oracle SQL Developer. Da SQL può essere impostato con:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Relazione di JDBC nella JVM ai cursori sul DB
Gli oggetti JDBC seguenti sono strettamente collegati ai seguenti concetti di database:
- JDBC Connessione è la rappresentazione client di una sessione del database e fornisce transazioni del database . Una connessione può avere una sola transazione aperta alla volta (ma le transazioni possono essere nidificate)
- Un ResultSet JDBC è supportato da un singolo cursore sulla banca dati. Quando viene chiamato close() sul ResultSet, il cursore viene rilasciato.
- Un CallableStatement JDBC richiama una procedura memorizzata sul database, spesso scritto in PL/SQL. La procedura memorizzata può creare zero o più cursori e può restituire un cursore come JDBC ResultSet.
JDBC è thread-safe:è abbastanza corretto passare i vari oggetti JDBC tra i thread.
Ad esempio, puoi creare la connessione in un thread; un altro thread può utilizzare questa connessione per creare una PreparedStatement e un terzo thread può elaborare il set di risultati. L'unica restrizione principale è che non puoi avere più di un ResultSet aperto su una singola PreparedStatement in qualsiasi momento. Consulta Oracle DB supporta più operazioni (parallele) per connessione?
Si noti che un commit del database si verifica su una connessione e quindi tutti i DML (INSERT, UPDATE e DELETE) su quella connessione verranno confermati insieme. Pertanto, se desideri supportare più transazioni contemporaneamente, devi disporre di almeno una Connessione per ciascuna Transazione simultanea.
Chiudere oggetti JDBC
Un tipico esempio di esecuzione di un ResultSet è:
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) { }
}
Nota come la clausola finally ignori qualsiasi eccezione sollevata da close():
- Se chiudi semplicemente ResultSet senza provare {} catch {}, potrebbe non riuscire e impedire la chiusura dell'istruzione
- Vogliamo consentire a qualsiasi eccezione sollevata nel corpo del tentativo di propagarsi al chiamante. Se hai un ciclo, ad esempio, durante la creazione e l'esecuzione di istruzioni, ricorda di chiudere ogni istruzione all'interno del ciclo.
In Java 7, Oracle ha introdotto l'interfaccia AutoCloseable che sostituisce la maggior parte del boilerplate Java 6 con del buon zucchero sintattico.
Contenere oggetti JDBC
Gli oggetti JDBC possono essere conservati in modo sicuro nelle variabili locali, nell'istanza dell'oggetto e nei membri della classe. In genere è meglio:
- Utilizza l'istanza dell'oggetto oi membri della classe per contenere oggetti JDBC che vengono riutilizzati più volte per un periodo più lungo, come Connections e PreparedStatements
- Utilizzare variabili locali per ResultSet poiché queste vengono ottenute, ripetute e quindi chiuse in genere nell'ambito di una singola funzione.
C'è, tuttavia, un'eccezione:se stai usando EJB o un contenitore Servlet/JSP, devi seguire un modello di threading rigoroso:
- Solo l'Application Server crea i thread (con i quali gestisce le richieste in arrivo)
- Solo il server delle applicazioni crea connessioni (ottenute dal pool di connessioni)
- Quando si salvano i valori (stato) tra le chiamate, è necessario prestare molta attenzione. Non archiviare mai valori nelle tue cache o membri statici:questo non è sicuro tra i cluster e altre condizioni strane e il server delle applicazioni potrebbe fare cose terribili ai tuoi dati. Utilizzare invece i bean con stato o un database.
- In particolare, mai tenere gli oggetti JDBC (Connections, ResultSet, PreparedStatements, ecc.) su diverse chiamate remote - lasciare che il server delle applicazioni gestisca questo. Il server delle applicazioni non solo fornisce un pool di connessioni, ma memorizza anche le tue PreparedStatements.
Eliminazione delle perdite
Sono disponibili numerosi processi e strumenti per rilevare ed eliminare le perdite JDBC:
-
Durante lo sviluppo, rilevare i bug in anticipo è di gran lunga l'approccio migliore:
-
Pratiche di sviluppo:le buone pratiche di sviluppo dovrebbero ridurre il numero di bug nel software prima che lasci la scrivania dello sviluppatore. Le pratiche specifiche includono:
- Programmazione in coppia, per educare chi non ha sufficiente esperienza
- Recensioni del codice perché molti occhi sono meglio di uno
- Unit test, il che significa che puoi esercitare qualsiasi e tutta la tua base di codice da uno strumento di test che rende banale la riproduzione delle perdite
- Utilizza le librerie esistenti per il pool di connessioni anziché crearne di tue
-
Analisi del codice statico:utilizza uno strumento come l'eccellente Findbugs per eseguire un'analisi del codice statico. Questo raccoglie molti punti in cui close() non è stato gestito correttamente. Findbugs ha un plug-in per Eclipse, ma funziona anche da solo per una tantum, ha integrazioni in Jenkins CI e altri strumenti di compilazione
-
-
In fase di esecuzione:
-
Holdability e impegno
- Se la conservabilità di ResultSet è ResultSet.CLOSE_CURSORS_OVER_COMMIT, il ResultSet viene chiuso quando viene chiamato il metodo Connection.commit(). Questo può essere impostato usando Connection.setHoldability() o usando il metodo Connection.createStatement() sovraccaricato.
-
Registrazione in fase di esecuzione.
- Inserisci buone istruzioni di registro nel tuo codice. Questi dovrebbero essere chiari e comprensibili in modo che il cliente, il personale di supporto e i compagni di squadra possano capirli senza formazione. Dovrebbero essere concisi e includere la stampa dello stato/valori interni delle variabili chiave e degli attributi in modo da poter tracciare la logica di elaborazione. Una buona registrazione è fondamentale per il debug delle applicazioni, in particolare quelle che sono state distribuite.
-
Puoi aggiungere un driver JDBC di debug al tuo progetto (per il debug, non distribuirlo effettivamente). Un esempio (non l'ho usato) è log4jdbc. È quindi necessario eseguire alcune semplici analisi su questo file per vedere quali esecuzioni non hanno una chiusura corrispondente. Il conteggio delle aperture e delle chiusure dovrebbe evidenziare se c'è un potenziale problema
- Monitoraggio del database. Monitora la tua applicazione in esecuzione utilizzando strumenti come la funzione "Monitor SQL" di SQL Developer o TOAD di Quest. Il monitoraggio è descritto in questo articolo. Durante il monitoraggio, si interrogano i cursori aperti (ad es. dalla tabella v$sesstat) e si esamina il loro SQL. Se il numero di cursori aumenta e (soprattutto) viene dominato da un'unica istruzione SQL identica, sai di avere una perdita con quell'SQL. Cerca il tuo codice e rivedi.
-
Altri pensieri
Puoi usare WeakReferences per gestire la chiusura delle connessioni?
I riferimenti deboli e deboli sono modi per consentire di fare riferimento a un oggetto in un modo che consenta alla JVM di raccogliere il referente in qualsiasi momento lo ritenga opportuno (supponendo che non ci siano forti catene di riferimento per quell'oggetto).
Se si passa un ReferenceQueue nel costruttore al riferimento morbido o debole, l'oggetto viene posizionato in ReferenceQueue quando l'oggetto viene modificato in GC quando si verifica (se si verifica). Con questo approccio, puoi interagire con la finalizzazione dell'oggetto e puoi chiudere o finalizzare l'oggetto in quel momento.
I riferimenti fantasma sono un po' più strani; il loro scopo è solo quello di controllare la finalizzazione, ma non puoi mai ottenere un riferimento all'oggetto originale, quindi sarà difficile chiamare il metodo close() su di esso.
Tuttavia, raramente è una buona idea tentare di controllare quando viene eseguito il GC (Weak, Soft e PhantomReferences ti informano dopo che l'oggetto è accodato per GC). In effetti, se la quantità di memoria nella JVM è grande (ad es. -Xmx2000m) potresti mai GC l'oggetto e sperimenterai ancora ORA-01000. Se la memoria JVM è piccola rispetto ai requisiti del tuo programma, potresti scoprire che gli oggetti ResultSet e PreparedStatement vengono GCed immediatamente dopo la creazione (prima che tu possa leggerli), il che probabilmente non riuscirà il tuo programma.
TL;DR: Il debole meccanismo di riferimento non è un buon modo per gestire e chiudere gli oggetti Statement e ResultSet.