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 cursores configurados em uma instância de banco de dados.
As causas comuns são:
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 maior que o número de cursores no banco de dados.
- Você tem muitos desenvolvedores ou aplicativos conectados à mesma instância de banco de dados (que provavelmente incluirá muitos esquemas) e juntos estão usando muitas conexões.
Solução:
Vazamento do cursor
- Os aplicativos não estão fechando ResultSets (em JDBC) ou cursores (em procedimentos armazenados no banco de dados)
- Solução : Os vazamentos do 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 .
fundo
Esta seção descreve parte da teoria por trás dos cursores e como o JDBC deve ser usado. Se você não precisa saber o plano de fundo, pode pular isso e ir direto para 'Eliminar vazamentos'.
O que é um cursor?
Um cursor é um recurso no banco de dados que manté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 um com várias sessões . Para fazer isso, ele 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 exige 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 . No SQL, ele 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:
- A Conexão JDBC é a representação do cliente de uma sessão de banco de dados e fornece transações de banco de dados . Uma conexão pode ter apenas uma única transação aberta a qualquer momento (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, geralmente escrito em PL / SQL. O procedimento armazenado pode criar zero ou mais cursores e pode retornar um cursor como um ResultSet JDBC.
JDBC é thread-safe: é normal passar os vários objetos JDBC entre threads.
Por exemplo, você pode criar a conexão em um segmento; outro encadeamento pode usar essa conexão para criar um PreparedStatement e um terceiro encadeamento 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 banco de dados Oracle oferece suporte a várias operações (paralelas) por conexão?
Observe que uma confirmação de banco de dados ocorre em uma conexão e, portanto, todos os DML (INSERT, UPDATE e DELETE) nessa conexão serão confirmados juntos. Portanto, se você deseja oferecer suporte a 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 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 gerada pelo close ():
- Se você simplesmente fechar o ResultSet sem tentar {} catch {}, ele pode falhar e impedir que a instrução seja fechada
- Queremos permitir que qualquer exceção levantada no corpo da tentativa se propague 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 padrão Java 6 por alguns bons açúcares sintáticos.
Segurando objetos JDBC
Os 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 manter 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, uma vez que elas são obtidas, executadas em loop e fechadas normalmente dentro do escopo de uma única função.
No entanto, há uma exceção: se você estiver usando EJBs ou um contêiner Servlet / JSP, deverá seguir um modelo de threading rígido:
- Apenas o servidor de aplicativos cria threads (com os quais lida com as solicitações de entrada)
- Apenas o Application Server cria conexões (que você obtém do pool de conexão)
- 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 servidor de aplicativos 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ão, mas também armazena em cache seus PreparedStatements.
Eliminando vazamentos
Existem vários processos e ferramentas disponíveis para ajudar a detectar e eliminar vazamentos de JDBC:
Durante o desenvolvimento - detectar bugs no início é de longe a melhor abordagem:
Práticas de desenvolvimento: Boas práticas de desenvolvimento devem reduzir o número de bugs em seu software antes que ele deixe a mesa do desenvolvedor. As práticas específicas incluem:
- Programação em pares , para educar aqueles sem experiência suficiente
- Revisões de código porque muitos olhos são melhores do que um
- 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
- Use bibliotecas existentes para pool de conexão ao invés de construir seu próprio
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 funciona de forma autônoma para eventos pontuais, tem integrações com Jenkins CI e outras ferramentas de construção
Em tempo de execução:
Capacidade de retenção e compromisso
- Se a capacidade de manutençã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 ().
Registrando em tempo de execução.
- Coloque boas instruções de log em seu código. Eles 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 é fundamental para depurar aplicativos, especialmente aqueles que foram implantados.
Você pode adicionar um driver JDBC de depuração ao seu projeto (para depuração - não o implemente). Um exemplo (eu não usei) é log4jdbc . Em seguida, você 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 potencial
- Monitorando o banco de dados. Monitore seu aplicativo em execução usando ferramentas como a função SQL Developer 'Monitor SQL' ou o Quest's TOAD . 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 (o mais importante) tornando-se dominado por uma instrução SQL idêntica, você sabe que há um vazamento com esse SQL. Pesquise seu código e analise.
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 a qualquer momento que julgar adequado (assumindo que não haja cadeias de referência fortes para esse objeto).
Se você passar um ReferenceQueue no construtor para a referência suave ou fraca, o objeto é colocado no ReferenceQueue quando o objeto é GC'ed quando ocorre (se ocorrer). Com essa abordagem, você pode interagir com a finalização do objeto e pode fechar ou finalizar o objeto naquele momento.
As 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 ideia tentar controlar quando o GC é executado (Weak, Soft e PhantomReferences permitem que você saiba após o fato de que o objeto está enfileirado para GC). Na verdade, se a quantidade de memória na JVM for grande (por exemplo, -Xmx2000m), você pode nunca fazer o GC no objeto e ainda terá o ORA-01000. Se a memória da JVM for pequena em relação aos requisitos do seu programa, você pode descobrir que os objetos ResultSet e PreparedStatement são submetidos ao GC imediatamente após a criação (antes que você possa ler a partir deles), o que provavelmente irá falhar seu programa.
TL; DR: O mecanismo de referência fraca não é uma boa maneira de gerenciar e fechar objetos Statement e ResultSet.
for (String language : additionalLangs) {