ORA-01000, l'erreur maximum-open-cursors, 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 une connexion et un pool de threads plus grands que le nombre de curseurs de 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 comprendra probablement de nombreux schémas) et, ensemble, vous utilisez trop de connexions.
Solution:
Fuite de curseur
- Les applications ne ferment 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; l'augmentation du 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 l'arrière-plan, vous pouvez l'ignorer et aller directement à «Éliminer les 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 d'un lecteur dans un ResultSet. Chaque instruction SELECT possède un curseur et les procédures stockées PL / SQL peuvent ouvrir et utiliser autant de curseurs que nécessaire. 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 ayant chacun 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 arrive qui nécessite un nouveau curseur, la demande échoue avec une erreur ORA-010000.
Recherche et définition du 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 . Depuis SQL, il peut être défini avec:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Association de JDBC dans la JVM aux curseurs de la base de données
Les objets JDBC ci-dessous sont étroitement associés aux concepts de base de données suivants:
- JDBC Connection est la représentation client d'une session de base de données et fournit des transactions de base de données . Une connexion ne peut avoir qu'une seule transaction ouverte à la fois (mais les transactions peuvent être imbriquées)
- Un ResultSet JDBC est pris en charge par un seul curseur sur la base de données. Lorsque close () est appelé sur le ResultSet, le curseur est relâché.
- Un CallableStatement JDBC appelle une procédure stockée sur la base de données, souvent écrite en PL / SQL. La procédure stockée peut créer zéro ou plusieurs curseurs et peut renvoyer un curseur en tant que ResultSet JDBC.
JDBC est thread-safe: il est tout à fait normal 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 avoir 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 déclenchée par close ():
- Si vous fermez simplement le ResultSet sans essayer {} catch {}, il peut échouer et empêcher la fermeture de l'instruction
- Nous voulons permettre à toute exception déclenchée dans le corps de la tentative de se propager à l'appelant. Si vous avez une boucle sur, 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 plupart des standards Java 6 par un bon sucre syntaxique.
Tenir 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 ils 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 demandes entrantes)
- Seul le serveur d'applications crée des connexions (que vous obtenez à partir du pool de connexions)
- Lors de l'enregistrement 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 entre les clusters et d'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, ne gardez jamais d' objets JDBC (Connexions, 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, il met également en cache vos PreparedStatements.
É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 - attraper les bogues tôt est de loin la meilleure approche:
Pratiques de développement: Les bonnes pratiques de développement doivent réduire le nombre de bogues dans votre logiciel avant qu'il ne quitte le bureau du développeur. Les pratiques spécifiques comprennent:
- Programmation en binôme , pour éduquer ceux qui n'ont pas d'expérience suffisante
- Revues de code parce que plusieurs yeux valent mieux qu'un
- Test unitaire, ce qui signifie que vous pouvez exercer tout ou partie de votre base de code à partir d'un outil de test qui rend la reproduction des fuites triviale
- Utilisez les 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 les excellents Findbugs pour effectuer une analyse de code statique. Cela ramasse de nombreux endroits où la fermeture () n'a pas été correctement gérée. Findbugs a un plugin pour Eclipse, mais il fonctionne également de manière autonome pour des opérations ponctuelles, a des intégrations dans Jenkins CI et d'autres outils de construction
Lors de l'exécution:
Holdability et engagement
- Si le holdability ResultSet est ResultSet.CLOSE_CURSORS_OVER_COMMIT, le ResultSet est fermé lorsque la méthode Connection.commit () est appelée. Cela peut être défini à l'aide de Connection.setHoldability () ou de la méthode surchargée Connection.createStatement ().
Journalisation au moment de l'exécution.
- Mettez de bonnes déclarations 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 de l'état / des valeurs internes des variables clés et des attributs 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 faire une analyse simple sur ce fichier pour voir quelles exécutions n'ont pas de fermeture correspondante. Le comptage des ouvertures et des fermetures doit 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 le TOAD de Quest . 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 révisez 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 révisez.
D'autres pensées
Pouvez-vous utiliser WeakReferences pour gérer les connexions de fermeture?
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 bon (en supposant qu'il n'y a pas de chaînes de référence solides pour 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 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 de référence à l'objet d'origine, il sera donc difficile d'appeler la méthode close () dessus.
Cependant, il est rarement judicieux de tenter de contrôler le moment où le GC est exécuté (les références faibles, souples et fantômes vous informent après le fait que l'objet est mis en file d'attente pour GC). En fait, si la quantité de mémoire dans la JVM est grande (par exemple -Xmx2000m), vous risquez de ne jamais GC l'objet, et vous rencontrerez toujours 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 mis en GC immédiatement après leur création (avant que vous 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.
for (String language : additionalLangs) {