ORA-01000, l'erreur d'ouverture maximale des curseurs, est une erreur extrêmement courante dans le développement de bases de données Oracle. Dans le contexte de Java, cela se produit lorsque l'application tente d'ouvrir plus de ResultSets qu'il n'y a de curseurs configurés sur une instance de base de données.
Les causes courantes sont :
-
Erreur de configuration
- Vous avez plus de threads dans votre application interrogeant la base de données que de curseurs sur la base de données. Un cas est celui où vous avez un pool de connexions et de threads supérieur au nombre de curseurs sur la base de données.
- Vous avez de nombreux développeurs ou applications connectés à la même instance de base de données (qui inclura probablement de nombreux schémas) et ensemble, vous utilisez trop de connexions.
-
Solution :
- Augmenter le nombre de curseurs sur la base de données (si les ressources le permettent) ou
- Réduire le nombre de threads dans l'application.
-
Fuite de curseur
- L'application ne ferme pas les ResultSets (dans JDBC) ou les curseurs (dans les procédures stockées sur la base de données)
- Solution :Les fuites de curseur sont des bogues; augmenter le nombre de curseurs sur la base de données retarde simplement l'échec inévitable. Les fuites peuvent être détectées à l'aide de l'analyse de code statique, de la journalisation JDBC ou au niveau de l'application et de la surveillance de la base de données.
Contexte
Cette section décrit une partie de la théorie derrière les curseurs et comment JDBC doit être utilisé. Si vous n'avez pas besoin de connaître le contexte, vous pouvez ignorer ceci et passer directement à "Élimination des fuites".
Qu'est-ce qu'un curseur ?
Un curseur est une ressource sur la base de données qui contient l'état d'une requête, en particulier la position où se trouve un lecteur dans un ResultSet. Chaque instruction SELECT a un curseur et les procédures stockées PL/SQL peuvent ouvrir et utiliser autant de curseurs qu'elles le souhaitent. Vous pouvez en savoir plus sur les curseurs sur Orafaq.
Une instance de base de données sert généralement plusieurs schémas différents , de nombreux utilisateurs différents chacun avec plusieurs sessions . Pour ce faire, il dispose d'un nombre fixe de curseurs disponibles pour tous les schémas, utilisateurs et sessions. Lorsque tous les curseurs sont ouverts (en cours d'utilisation) et qu'une demande nécessite un nouveau curseur, la demande échoue avec une erreur ORA-010000.
Rechercher et définir le nombre de curseurs
Le numéro est normalement configuré par le DBA lors de l'installation. Le nombre de curseurs actuellement utilisés, le nombre maximum et la configuration sont accessibles dans les fonctions Administrateur d'Oracle SQL Developer. À partir de SQL, il peut être défini avec :
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Relier JDBC dans la JVM aux curseurs sur la base de données
Les objets JDBC ci-dessous sont étroitement liés aux concepts de base de données suivants :
- JDBC Connexion est la représentation client d'une session de base de données et fournit la base de données transactions . Une connexion ne peut avoir qu'une seule transaction ouverte à la fois (mais les transactions peuvent être imbriquées)
- Un ensemble de résultats JDBC est supporté par un seul curseur sur la base de données. Lorsque close() est appelée sur le ResultSet, le curseur est relâché.
- Une instruction appelable JDBC appelle une procédure stockée sur la base de données, souvent écrit en PL/SQL. La procédure stockée peut créer zéro ou plusieurs curseurs et peut renvoyer un curseur sous la forme d'un jeu de résultats JDBC.
JDBC est thread-safe :il est tout à fait correct de passer les différents objets JDBC entre les threads.
Par exemple, vous pouvez créer la connexion dans un thread; un autre thread peut utiliser cette connexion pour créer un PreparedStatement et un troisième thread peut traiter le jeu de résultats. La seule restriction majeure est que vous ne pouvez pas avoir plus d'un ResultSet ouvert sur un seul PreparedStatement à tout moment. Voir Oracle DB prend-il en charge plusieurs opérations (parallèles) par connexion ?
Notez qu'une validation de base de données se produit sur une connexion, et donc tous les DML (INSERT, UPDATE et DELETE) sur cette connexion seront validés ensemble. Par conséquent, si vous souhaitez prendre en charge plusieurs transactions en même temps, vous devez disposer d'au moins une connexion pour chaque transaction simultanée.
Fermer les objets JDBC
Un exemple typique d'exécution d'un ResultSet est :
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) { }
}
Notez comment la clause finally ignore toute exception levée par close() :
- Si vous fermez simplement le ResultSet sans le try {} catch {}, cela peut échouer et empêcher la fermeture de la déclaration
- Nous souhaitons autoriser toute exception déclenchée dans le corps de try à se propager à l'appelant. Si vous avez une boucle, par exemple, la création et l'exécution d'instructions, n'oubliez pas de fermer chaque instruction dans la boucle.
Dans Java 7, Oracle a introduit l'interface AutoCloseable qui remplace la majeure partie du passe-partout Java 6 par un joli sucre syntaxique.
Conserver des objets JDBC
Les objets JDBC peuvent être conservés en toute sécurité dans des variables locales, des instances d'objet et des membres de classe. Il est généralement préférable de :
- Utilisez une instance d'objet ou des membres de classe pour contenir des objets JDBC qui sont réutilisés plusieurs fois sur une période plus longue, tels que Connections et PreparedStatements
- Utilisez des variables locales pour les ResultSets car ceux-ci sont obtenus, bouclés puis fermés généralement dans le cadre d'une seule fonction.
Il existe cependant une exception :si vous utilisez des EJB ou un conteneur Servlet/JSP, vous devez suivre un modèle de threading strict :
- Seul le serveur d'applications crée des threads (avec lesquels il gère les requêtes entrantes)
- Seul le serveur d'applications crée des connexions (que vous obtenez à partir du pool de connexions)
- Lorsque vous enregistrez des valeurs (état) entre les appels, vous devez être très prudent. Ne stockez jamais de valeurs dans vos propres caches ou membres statiques - ce n'est pas sûr dans les clusters et autres conditions étranges, et le serveur d'applications peut faire des choses terribles à vos données. Utilisez plutôt des beans avec état ou une base de données.
- En particulier, jamais maintenez les objets JDBC (Connections, ResultSets, PreparedStatements, etc.) sur différentes invocations à distance - laissez le serveur d'applications gérer cela. Le serveur d'applications fournit non seulement un pool de connexions, mais il met également en cache vos états préparés.
Éliminer les fuites
Il existe un certain nombre de processus et d'outils disponibles pour aider à détecter et éliminer les fuites JDBC :
-
Pendant le développement - détecter les bogues tôt est de loin la meilleure approche :
-
Pratiques de développement :de bonnes pratiques de développement devraient réduire le nombre de bogues dans votre logiciel avant qu'il ne quitte le bureau du développeur. Les pratiques spécifiques incluent :
- Programmation en binôme, pour éduquer ceux qui n'ont pas suffisamment d'expérience
- Vérifier le code, car plusieurs yeux valent mieux qu'un
- Tests unitaires, ce qui signifie que vous pouvez tester n'importe quelle base de code à partir d'un outil de test qui rend la reproduction des fuites triviale
- Utilisez des bibliothèques existantes pour le regroupement de connexions plutôt que de créer les vôtres
-
Analyse de code statique :Utilisez un outil comme l'excellent Findbugs pour effectuer une analyse de code statique. Cela récupère de nombreux endroits où le close() n'a pas été correctement géré. Findbugs a un plugin pour Eclipse, mais il fonctionne également de manière autonome pour les cas uniques, a des intégrations dans Jenkins CI et d'autres outils de construction
-
-
Lors de l'exécution :
-
Tenue et engagement
- Si la capacité de maintien du ResultSet est ResultSet.CLOSE_CURSORS_OVER_COMMIT, alors le ResultSet est fermé lorsque la méthode Connection.commit() est appelée. Cela peut être défini à l'aide de Connection.setHoldability() ou à l'aide de la méthode surchargée Connection.createStatement().
-
Journalisation au moment de l'exécution.
- Mettez de bonnes instructions de journal dans votre code. Celles-ci doivent être claires et compréhensibles afin que le client, le personnel d'assistance et les coéquipiers puissent comprendre sans formation. Ils doivent être concis et inclure l'impression des valeurs d'état/internes des variables et attributs clés afin que vous puissiez suivre la logique de traitement. Une bonne journalisation est fondamentale pour le débogage des applications, en particulier celles qui ont été déployées.
-
Vous pouvez ajouter un pilote JDBC de débogage à votre projet (pour le débogage - ne le déployez pas réellement). Un exemple (je ne l'ai pas utilisé) est log4jdbc. Vous devez ensuite effectuer une analyse simple sur ce fichier pour voir quelles exécutions n'ont pas de fermeture correspondante. Compter les ouvertures et les fermetures devrait mettre en évidence s'il y a un problème potentiel
- Surveillance de la base de données. Surveillez votre application en cours d'exécution à l'aide d'outils tels que la fonction SQL Developer 'Monitor SQL' ou Quest's TOAD. La surveillance est décrite dans cet article. Pendant la surveillance, vous interrogez les curseurs ouverts (par exemple à partir de la table v$sesstat) et passez en revue leur SQL. Si le nombre de curseurs augmente et (surtout) devient dominé par une instruction SQL identique, vous savez que vous avez une fuite avec ce SQL. Recherchez votre code et passez en revue.
-
Autres réflexions
Pouvez-vous utiliser WeakReferences pour gérer la fermeture des connexions ?
Les références faibles et souples sont des moyens de vous permettre de référencer un objet d'une manière qui permet à la JVM de récupérer le référent à tout moment qu'elle juge approprié (en supposant qu'il n'y ait pas de chaînes de référence fortes vers cet objet).
Si vous passez une ReferenceQueue dans le constructeur à la référence souple ou faible, l'objet est placé dans la ReferenceQueue lorsque l'objet est GC'ed lorsqu'il se produit (si cela se produit du tout). Avec cette approche, vous pouvez interagir avec la finalisation de l'objet et vous pouvez fermer ou finaliser l'objet à ce moment.
Les références fantômes sont un peu plus étranges; leur but est uniquement de contrôler la finalisation, mais vous ne pouvez jamais obtenir une référence à l'objet d'origine, il sera donc difficile d'appeler la méthode close() dessus.
Cependant, il est rarement judicieux d'essayer de contrôler le moment où le GC est exécuté (les références faibles, souples et fantômes vous permettent de savoir après coup que l'objet est mis en file d'attente pour GC). En fait, si la quantité de mémoire dans la JVM est importante (par exemple -Xmx2000m), vous ne pouvez jamais GC l'objet, et vous ferez toujours l'expérience de l'ORA-01000. Si la mémoire JVM est petite par rapport aux exigences de votre programme, vous pouvez constater que les objets ResultSet et PreparedStatement sont GCed immédiatement après la création (avant que vous ne puissiez les lire), ce qui échouera probablement votre programme.
TL;DR : Le mécanisme de référence faible n'est pas un bon moyen de gérer et de fermer les objets Statement et ResultSet.