ORA-01000, l'errore di massima apertura dei cursori, è un errore estremamente comune nello sviluppo del database Oracle. Nel contesto di Java, si verifica 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 che cursori sul DB. Un caso è quello in cui si dispone di una connessione e di un pool di thread più grande del numero di cursori nel database.
- Hai molti sviluppatori o applicazioni connesse alla stessa istanza database (che probabilmente includerà molti schemi) e insieme stai utilizzando troppe connessioni.
Soluzione:
Perdita del cursore
- Le applicazioni non chiudono i ResultSet (in JDBC) oi cursori (nelle procedure memorizzate sul database)
- Soluzione : le perdite del cursore sono bug; l'aumento del numero di cursori sul DB ritarda semplicemente l'inevitabile guasto. Le perdite possono essere trovate utilizzando l' analisi statica del codice , la registrazione JDBC o a livello di applicazione e il monitoraggio del database .
sfondo
Questa sezione descrive alcune delle teorie alla base dei cursori e come dovrebbe essere usato JDBC. Se non hai bisogno di conoscere lo sfondo, puoi saltare questo passaggio e andare direttamente a "Eliminazione delle 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 dispone di un cursore e le stored procedure PL / SQL possono essere aperte e utilizzare tutti i cursori necessari. Puoi trovare ulteriori informazioni sui cursori su Orafaq .
Un'istanza di database in genere serve diversi schemi diversi , molti utenti diversi ciascuno con più sessioni . Per fare ciò, ha un numero fisso di cursori disponibili per tutti gli schemi, utenti e 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 durante l'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;
Correlare JDBC nella JVM ai cursori nel DB
Gli oggetti JDBC di seguito sono strettamente collegati ai seguenti concetti di database:
- JDBC Connection è la rappresentazione client di una sessione di database e fornisce le transazioni di database . Una connessione può avere solo una singola transazione aperta alla volta (ma le transazioni possono essere nidificate)
- Un ResultSet JDBC è supportato da un singolo cursore nel database. Quando close () viene chiamato sul ResultSet, il cursore viene rilasciato.
- Una CallableStatement JDBC richiama una procedura memorizzata sul database, spesso scritta in PL / SQL. La procedura memorizzata può creare zero o più cursori e può restituire un cursore come ResultSet JDBC.
JDBC è thread-safe: è abbastanza OK 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 un PreparedStatement e un terzo thread può elaborare il set di risultati. L'unica limitazione principale è che non è possibile avere più di un ResultSet aperto su un singolo PreparedStatement in qualsiasi momento. Vedi Oracle DB supporta più operazioni (parallele) per connessione?
Si noti che un commit del database si verifica su una connessione, quindi tutti i DML (INSERT, UPDATE e DELETE) su quella connessione eseguiranno il commit insieme. Pertanto, se si desidera supportare più transazioni contemporaneamente, è necessario disporre di almeno una connessione per ciascuna transazione simultanea.
Chiusura di 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 finalmente ignori qualsiasi eccezione sollevata da close ():
- Se si chiude semplicemente il 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, creando ed eseguendo istruzioni, ricordati 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 un po 'di piacevole zucchero sintattico.
Tenere oggetti JDBC
Gli oggetti JDBC possono essere contenuti in modo sicuro in variabili locali, istanze di oggetti e membri di classi. In genere è meglio pratica:
- Utilizza l'istanza dell'oggetto o i membri della classe per conservare oggetti JDBC riutilizzati più volte per un periodo più lungo, come Connections e PreparedStatement
- Utilizzare le variabili locali per i ResultSet poiché questi vengono ottenuti, ripetuti e quindi chiusi normalmente nell'ambito di una singola funzione.
Esiste, tuttavia, un'eccezione: se si utilizzano EJB o un contenitore Servlet / JSP, è necessario seguire un modello di threading rigoroso:
- Solo il server delle applicazioni crea i thread (con i quali gestisce le richieste in arrivo)
- Solo il server delle applicazioni crea le connessioni (ottenute dal pool di connessioni)
- Quando salvi i valori (stato) tra le chiamate, devi stare molto attento. Non archiviare mai valori nelle proprie cache o membri statici: ciò non è sicuro tra cluster e altre condizioni strane e il server delle applicazioni potrebbe fare cose terribili ai dati. Utilizza invece bean stateful o un database.
- In particolare, non tenere mai oggetti JDBC (connessioni, ResultSet, PreparedStatement, ecc.) Su diverse chiamate remote: lasciare che sia il server delle applicazioni a gestirlo. Il server delle applicazioni non solo fornisce un pool di connessioni, ma memorizza anche nella cache le PreparedStatement.
Eliminare le perdite
Sono disponibili numerosi processi e strumenti per aiutare a rilevare ed eliminare le fughe di JDBC:
Durante lo sviluppo, individuare tempestivamente i bug è di gran lunga l'approccio migliore:
Pratiche di sviluppo: 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 esperienza sufficiente
- Revisioni del codice perché molti occhi sono meglio di uno
- Test unitario, il che significa che puoi esercitare qualsiasi codice di base da uno strumento di test che rende banale la riproduzione delle perdite
- Usa le librerie esistenti per il pool di connessioni piuttosto che crearne di tue
Analisi statica del codice: utilizza uno strumento come l'eccellente Findbugs per eseguire un'analisi statica del codice. Questo rileva 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 conservazione del ResultSet è ResultSet.CLOSE_CURSORS_OVER_COMMIT, il ResultSet viene chiuso quando viene chiamato il metodo Connection.commit (). Questo può essere impostato utilizzando Connection.setHoldability () o utilizzando il metodo Connection.createStatement () sovraccarico.
Registrazione in fase di esecuzione.
- Metti buone dichiarazioni di log nel tuo codice. Questi dovrebbero essere chiari e comprensibili in modo che il cliente, il personale di supporto ei membri del team possano capire senza formazione. Dovrebbero essere concisi e includere la stampa dei valori interni / di stato delle variabili e degli attributi chiave 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. Contare l'apertura e la chiusura dovrebbe evidenziare se c'è un potenziale problema
- Monitoraggio del database. Monitorare l'applicazione in esecuzione utilizzando strumenti quali la funzione "Monitor SQL" di SQL Developer o il TOAD di Quest . Il monitoraggio è descritto in questo articolo . Durante il monitoraggio, interroghi i cursori aperti (ad esempio dalla tabella v $ sesstat) e rivedi il loro SQL. Se il numero di cursori aumenta e (cosa più importante) viene dominato da un'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 morbidi sono modi per consentire di fare riferimento a un oggetto in un modo che consente alla JVM di raccogliere in modo inutile il referente in qualsiasi momento lo ritenga adatto (supponendo che non ci siano catene di riferimento forti a quell'oggetto).
Se si passa una ReferenceQueue nel costruttore al riferimento debole o debole, l'oggetto viene posizionato in ReferenceQueue quando l'oggetto viene sottoposto a GC quando si verifica (se si verifica affatto). 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 il GC è in esecuzione (Weak, Soft e PhantomReferences ti informano dopo il fatto che l'oggetto è stato accodato per GC). In effetti, se la quantità di memoria nella JVM è grande (es. -Xmx2000m) potresti non mettere mai in GC l'oggetto e continuerai a sperimentare ORA-01000. Se la memoria JVM è piccola rispetto ai requisiti del programma, è possibile che gli oggetti ResultSet e PreparedStatement vengano sottoposti a GC immediatamente dopo la creazione (prima che sia possibile leggere da essi), il che probabilmente fallirà il programma.
TL; DR: il meccanismo di riferimento debole non è un buon modo per gestire e chiudere gli oggetti Statement e ResultSet.
for (String language : additionalLangs) {