web-dev-qa-db-fra.com

Oracle ne supprime pas les curseurs après la fermeture du jeu de résultats

Remarque: nous réutilisons une seule connexion.

************************************************
public Connection connection() {        
    try {
        if ((connection == null) || (connection.isClosed()))
        {
            if (connection!=null)
                log.severe("Connection was closed !");
            connection = DriverManager.getConnection(jdbcURL, username, password);
        }
    } catch (SQLException e) {
        log.severe("can't connect: " + e.getMessage());
    }
    return connection;        
}
**************************************************

public IngisObject[] select(String query, String idColumnName, String[] columns) {
    Connection con = connection();

    Vector<IngisObject> objects = new Vector<IngisObject>();
    try {
        Statement stmt = con.createStatement();

        String sql = query;
        ResultSet rs =stmt.executeQuery(sql);//Oracle increases cursors count here
        while(rs.next()) {
            IngisObject o = new IngisObject("New Result");
            o.setIdColumnName(idColumnName);            
            o.setDatabase(this);
            for(String column: columns)
                o.attrs().put(column, rs.getObject(column));
            objects.add(o);
        }

        rs.close();// Oracle don't decrease cursor count here, while it's expected
        stmt.close();
    } 
    catch (SQLException ex) {
        System.out.println(query);
        ex.printStackTrace();
    }
18
Vladimir

Le paramètre init.ora open_cursors définit le nombre maximum de curseurs ouverts qu'une session peut avoir simultanément. Il a une valeur par défaut de 50. Si l'application dépasse ce nombre, l'erreur "ORA-01000: dépassement du nombre maximal de curseurs ouverts" est générée.

Par conséquent, il est obligatoire de fermer les ressources JDBC lorsqu'elles ne sont plus nécessaires, en particulier Java.sql.ResultSet et Java.sql.Statement. S'ils ne sont pas fermés, l'application présente une fuite de ressources.

En cas de réutilisation de l'objet Connection, vous devez être conscient du fait que les curseurs Oracle ouverts sont maintenus ouverts et utilisés tant que la connexion existe et la transaction n'est pas terminée. Lorsque l'application est validée, les curseurs ouverts sont libérés.

Par conséquent, en tant que concepteur d'applications, vous devez connaître une estimation approximative des curseurs ouverts nécessaires pour votre transaction la plus complexe.

La difficulté réside dans l'incapacité des vues de paramètres internes d'Oracle (v $ open_cursor, v $ sesstat, et al.) De montrer la différence entre les curseurs ouverts, qui sont réutilisables et les curseurs ouverts, qui sont toujours bloqués (non réutilisables!) Par un ResulSet ou une déclaration non divulguée. Si vous fermez tous les objets Statement et ResultSet dans votre bloc enfin, votre application est parfaitement bien.

Le réglage du paramètre init.ora fonctionne comme ceci (notre application a besoin de 800 curseurs au maximum)

ALTER SYSTEM SET open_cursors = 800 SCOPE=BOTH;
24
Oliver Michels

Normalement, vous placeriez les instructions close pour votre ResultSet et votre instruction dans un bloc finally pour vous assurer qu'elles sont appelées même si une exception se produit (cela pourrait être le problème que vous rencontrez ici). Dans votre code actuel, si une exception SQLException se produit, les deux appels de méthode close () ne se produiront jamais et les curseurs resteront ouverts.

Quelle requête utilisez-vous également dans Oracle pour voir le nombre de curseurs ouverts?

Modifier:
Ce code devrait fermer le curseur. Si ce n'est pas le cas, vous devriez pouvoir voir une corrélation 1 à 1 d'appeler votre méthode et le nombre de curseurs augmenter de 1. Assurez-vous qu'aucun processus inattendu n'entraîne une augmentation du nombre de curseurs.

Si vous avez les privilèges, vous pouvez exécuter cette requête sur la base de données pour voir le nombre de curseurs ouverts par Sid pour voir si c'est peut-être un autre processus qui augmente les curseurs et pas le vôtre en particulier. Il en retirera un avec plus de 10 curseurs ouverts, vous pouvez augmenter cela pour filtrer le bruit ou le réduire spécifiquement par nom d'utilisateur ou osuser:

select oc.sid,
       count(*) numCur,
       s.username username,
       s.osuser osuser,
       oc.sql_text,
       s.program
  from v$open_cursor oc,
       v$session s
 where s.sid = oc.sid
group by oc.sid, 
         oc.sql_text, 
         s.username, 
         s.osuser, 
         s.program
having count(*) > 10
order by oc.sid;

Une autre requête qui peut être utile, dans le cas où plusieurs Sid utilisent la même chaîne de requête afin que ce qui précède ne révèle pas bien le délinquant:

 select oc.sql_text, count(*) 
   from v$open_cursor oc 
   group by oc.sql_text 
   having count(*) > 10 
   order by count(*) desc;
7
Doug Porter

La façon correcte de le faire est de fermer chaque ressource dans un bloc finally dans son propre bloc try/catch. J'utilise généralement une classe utilitaire statique comme celle-ci:

public class DatabaseUtils
{
    public static void close(Connection connection)
    {
        try
        {
            if (connection != null)
            {
                connection.close();
            }
        }
        catch (SQLException e)
        {
            // log exception here.
        }
    }

    // similar methods for ResultSet and Statement
}

J'écrirais donc votre code comme ceci:

public IngisObject[] select(String query, String idColumnName, String[] columns) {

Vector<IngisObject> objects = new Vector<IngisObject>();

Connection con = null;
Statement stmt = null;
ResultSet rs = null;

try 
{
    connection = connection();
    stmt = con.createStatement();

    // This is a SQL injection attack waiting to happen; I'd recommend PreparedStatemen
    String sql = query;
    rs =stmt.executeQuery(sql);//Oracle increases cursors count here
    while(rs.next()) 
    {
       IngisObject o = new IngisObject("New Result");
       o.setIdColumnName(idColumnName);            
       o.setDatabase(this);
       for(String column: columns) o.attrs().put(column, rs.getObject(column));
       objects.add(o);
    }

} 
catch (SQLException ex) 
{
    System.out.println(query);
    ex.printStackTrace();
}
finally
{
    DatabaseUtils.close(rs);
    DatabaseUtils.close(stmt);
    DatabaseUtils.close(con);
}
6
duffymo

J'ai juste eu le même problème et j'ai trouvé que - si vous ne fermez pas la connexion (parce que vous pourrez peut-être la réutiliser plus tard) - vous avez au moins faire un connection.rollback () ou connection.commit () pour libérer les curseurs ouverts en fermant le ResultSet et les instructions.

6
FrVaBe