Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

java.sql.SQLException:- ORA-01000:cursores abertos máximos excedidos


ORA-01000, o erro máximo de cursores abertos, é um erro extremamente comum no desenvolvimento de banco de dados Oracle. No contexto do Java, isso acontece quando o aplicativo tenta abrir mais ResultSets do que os cursores configurados em uma instância do banco de dados.

As causas comuns são:

  1. Erro de configuração
    • Você tem mais threads em seu aplicativo consultando o banco de dados do que cursores no banco de dados. Um caso é quando você tem uma conexão e um pool de threads maiores que o número de cursores no banco de dados.
    • Você tem muitos desenvolvedores ou aplicativos conectados à mesma instância de banco de dados (o que provavelmente incluirá muitos esquemas) e, juntos, você está usando muitas conexões.

    • Solução:
      • Aumentar o número de cursores no banco de dados (se os recursos permitirem) ou
      • Diminuindo o número de threads no aplicativo.

  2. Vazamento do cursor
    • Os aplicativos não estão fechando ResultSets (em JDBC) ou cursores (em procedimentos armazenados no banco de dados)
    • Solução :Vazamentos de cursor são bugs; aumentar o número de cursores no banco de dados simplesmente atrasa a falha inevitável. Vazamentos podem ser encontrados usando análise de código estático, JDBC ou registro em nível de aplicativo e monitoramento de banco de dados.

Plano de fundo


Esta seção descreve um pouco da teoria por trás dos cursores e como o JDBC deve ser usado. Se você não precisa conhecer os antecedentes, pode pular isso e ir direto para 'Eliminar Vazamentos'.

O que é um cursor?


Um cursor é um recurso no banco de dados que contém o estado de uma consulta, especificamente a posição em que um leitor está em um ResultSet. Cada instrução SELECT tem um cursor e os procedimentos armazenados PL/SQL podem abrir e usar quantos cursores forem necessários. Você pode descobrir mais sobre cursores no Orafaq.

Uma instância de banco de dados normalmente atende a vários esquemas diferentes , muitos usuários diferentes cada uma com várias sessões . Para isso, possui um número fixo de cursores disponíveis para todos os esquemas, usuários e sessões. Quando todos os cursores estão abertos (em uso) e chega uma solicitação que requer um novo cursor, a solicitação falha com um erro ORA-010000.

Encontrar e definir o número de cursores


O número é normalmente configurado pelo DBA na instalação. O número de cursores atualmente em uso, o número máximo e a configuração podem ser acessados ​​nas funções do Administrador no Oracle SQL Developer. A partir do SQL, pode ser definido com:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;

Relacionando JDBC na JVM a cursores no banco de dados


Os objetos JDBC abaixo são fortemente acoplados aos seguintes conceitos de banco de dados:
  • JDBC Conexão é a representação do cliente de um banco de dados sessão e fornece banco de dados transações . Uma conexão pode ter apenas uma única transação aberta por vez (mas as transações podem ser aninhadas)
  • Um ResultSet JDBC é suportado por um único cursor no banco de dados. Quando close() é chamado no ResultSet, o cursor é liberado.
  • Um JDBC CallableStatement invoca um procedimento armazenado no banco de dados, muitas vezes escrito em PL/SQL. O procedimento armazenado pode criar zero ou mais cursores e pode retornar um cursor como um JDBC ResultSet.

JDBC é thread-safe:Não há problema em passar os vários objetos JDBC entre threads.

Por exemplo, você pode criar a conexão em um thread; outro thread pode usar essa conexão para criar um PreparedStatement e um terceiro thread pode processar o conjunto de resultados. A única restrição principal é que você não pode ter mais de um ResultSet aberto em um único PreparedStatement a qualquer momento. Consulte O Oracle DB oferece suporte a várias operações (paralelas) por conexão?

Observe que um commit do banco de dados ocorre em uma conexão e, portanto, todos os DML (INSERT, UPDATE e DELETE's) nessa conexão serão confirmados juntos. Portanto, se você deseja suportar várias transações ao mesmo tempo, deve ter pelo menos uma Conexão para cada Transação simultânea.

Fechando objetos JDBC


Um exemplo típico de execução de um 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) { }
}

Observe como a cláusula finally ignora qualquer exceção levantada pelo close():
  • Se você simplesmente fechar o ResultSet sem tentar {} catch {}, ele poderá falhar e impedir que a instrução seja fechada
  • Queremos permitir que qualquer exceção gerada no corpo da tentativa seja propagada para o chamador. Se você tiver um loop, por exemplo, criando e executando instruções, lembre-se de fechar cada instrução dentro do loop.

No Java 7, a Oracle introduziu a interface AutoCloseable que substitui a maior parte do clichê do Java 6 por um bom açúcar sintático.

Retendo objetos JDBC


Objetos JDBC podem ser mantidos com segurança em variáveis ​​locais, instância de objeto e membros de classe. Geralmente, é uma prática melhor:
  • Use instância de objeto ou membros de classe para armazenar objetos JDBC que são reutilizados várias vezes por um período mais longo, como Connections e PreparedStatements
  • Use variáveis ​​locais para ResultSets, pois eles são obtidos, repetidos e fechados normalmente dentro do escopo de uma única função.

Há, no entanto, uma exceção:se você estiver usando EJBs ou um contêiner Servlet/JSP, deverá seguir um modelo de encadeamento estrito:
  • Somente o servidor de aplicativos cria threads (com os quais lida com solicitações recebidas)
  • Somente o servidor de aplicativos cria conexões (que você obtém do pool de conexões)
  • Ao salvar valores (estado) entre chamadas, você deve ter muito cuidado. Nunca armazene valores em seus próprios caches ou membros estáticos - isso não é seguro em clusters e outras condições estranhas, e o Application Server pode fazer coisas terríveis com seus dados. Em vez disso, use beans com estado ou um banco de dados.
  • Em particular, nunca mantenha objetos JDBC (Connections, ResultSets, PreparedStatements, etc) em diferentes invocações remotas - deixe o Application Server gerenciar isso. O Application Server não apenas fornece um pool de conexões, mas também armazena em cache suas PreparedStatements.

Eliminando vazamentos


Há vários processos e ferramentas disponíveis para ajudar a detectar e eliminar vazamentos de JDBC:

  1. Durante o desenvolvimento - detectar bugs cedo é de longe a melhor abordagem:

    1. Práticas de desenvolvimento:As boas práticas de desenvolvimento devem reduzir o número de bugs em seu software antes que ele saia da mesa do desenvolvedor. As práticas específicas incluem:
      1. Programação em pares, para educar aqueles sem experiência suficiente
      2. Revisões de código porque muitos olhos pensam melhor que um
      3. Teste de unidade, o que significa que você pode exercitar toda e qualquer base de código a partir de uma ferramenta de teste que torna a reprodução de vazamentos trivial
      4. Use bibliotecas existentes para pool de conexões em vez de criar suas próprias

    2. Análise de código estático:Use uma ferramenta como o excelente Findbugs para realizar uma análise de código estático. Isso pega muitos lugares onde o close() não foi tratado corretamente. Findbugs tem um plug-in para Eclipse, mas também é executado de forma autônoma, possui integrações no Jenkins CI e outras ferramentas de compilação

  2. Em tempo de execução:

    1. Capacidade de retenção e compromisso
      1. Se a capacidade de retenção do ResultSet for ResultSet.CLOSE_CURSORS_OVER_COMMIT, o ResultSet será fechado quando o método Connection.commit() for chamado. Isso pode ser definido usando Connection.setHoldability() ou usando o método sobrecarregado Connection.createStatement().

    2. Registro em tempo de execução.
      1. Coloque boas instruções de log em seu código. Estes devem ser claros e compreensíveis para que o cliente, a equipe de suporte e os colegas de equipe possam entender sem treinamento. Eles devem ser concisos e incluir a impressão dos valores de estado/internos de variáveis ​​e atributos-chave para que você possa rastrear a lógica de processamento. Um bom registro em log é fundamental para depurar aplicativos, especialmente aqueles que foram implantados.

      2. Você pode adicionar um driver JDBC de depuração ao seu projeto (para depuração - não o implante). Um exemplo (não usei) é log4jdbc. Você então precisa fazer uma análise simples neste arquivo para ver quais execuções não têm um fechamento correspondente. A contagem de aberturas e fechamentos deve destacar se há um problema em potencial
        1. Monitorando o banco de dados. Monitore seu aplicativo em execução usando ferramentas como a função 'Monitor SQL' do SQL Developer ou o TOAD da Quest. O monitoramento é descrito neste artigo. Durante o monitoramento, você consulta os cursores abertos (por exemplo, da tabela v$sesstat) e revisa seu SQL. Se o número de cursores está aumentando e (mais importante) se tornando dominado por uma instrução SQL idêntica, você sabe que tem um vazamento com esse SQL. Pesquise seu código e revise.

Outros pensamentos

Você pode usar WeakReferences para lidar com o fechamento de conexões?


Referências fracas e suaves são maneiras de permitir que você faça referência a um objeto de uma maneira que permita que a JVM colete o referente no lixo a qualquer momento que julgar adequado (supondo que não haja cadeias de referência fortes para esse objeto).

Se você passar uma ReferenceQueue no construtor para a referência suave ou fraca, o objeto será colocado na ReferenceQueue quando o objeto for GC'ed quando ocorrer (se ocorrer). Com esta abordagem, você pode interagir com a finalização do objeto e pode fechar ou finalizar o objeto naquele momento.

Referências fantasmas são um pouco mais estranhas; seu propósito é apenas controlar a finalização, mas você nunca pode obter uma referência ao objeto original, então será difícil chamar o método close() nele.

No entanto, raramente é uma boa idéia tentar controlar quando o GC é executado (Weak, Soft e PhantomReferences informam após o fato que o objeto é enfileirado para GC). Na verdade, se a quantidade de memória na JVM for grande (por exemplo, -Xmx2000m), você pode nunca Faça o GC do objeto e você ainda experimentará o ORA-01000. Se a memória da JVM for pequena em relação aos requisitos do seu programa, você poderá descobrir que os objetos ResultSet e PreparedStatement são GCed imediatamente após a criação (antes que você possa ler a partir deles), o que provavelmente falhará em seu programa.

TL;DR: O mecanismo de referência fraco não é uma boa maneira de gerenciar e fechar objetos Statement e ResultSet.