Junto com o clássico OutOfMemoryError
, este é um dos erros que mais aparece em sistemas que usam Oracle. Resolver esse problema não é difícil, como vou explicar aqui.
Este erro é causado por Connection
s, Statement
s e ResultSet
s deixados abertos. Normalmente temos algo como:
Connection con = //obtem conexao;
PreparedStatement ps = con.prepareStatement("SELECT ...");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
// ...
}
rs.close();
ps.close();
con.close();
Note que eu estou sendo otimista, supondo que PreparedStatement
s sejam usados ao invés de concatenar String
s para executar consultas e que o método close()
nas três instâncias esteja pelo menos no código. Se você não fez nem isso, corra atrás. :-)
O bug é que, quando ocorre uma exceção, durante o processamento do select, nenhum dos três objetos é fechado. Então, a primeira solução é colocar o fechamento num bloco try
/finally
, como mostrado abaixo:
Connection con = null;
PreparedStatement ps = null;
ResultSet rs =null;
try {
con = //obtem conexao;
ps = con.prepareStatement("SELECT ...");
rs = ps.executeQuery();
while (rs.next()) {
// ...
}
} finally {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (con != null) {
con.close();
}
}
Tudo certo, né? Errado. Se ocorrer uma exceção ao fechar rs
, ps
e con
nunca serão fechados. Então a melhor solução seria:
Connection con = null;
PreparedStatement ps = null;
ResultSet rs =null;
try {
con = //obtem conexao;
ps = con.prepareStatement("SELECT ...");
rs = ps.executeQuery();
while (rs.next()) {
// ...
}
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException sqe) {
// logar excecao
} catch (RuntimeException re) {
// logar excecao
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException sqe) {
// logar excecao
} catch (RuntimeException re) {
// logar excecao
}
}
if (con != null) {
try {
con.close();
} catch (SQLException sqe) {
// logar excecao
} catch (RuntimeException re) {
// logar excecao
}
}
}
Ok, você vai me dizer, muito legal isso. Mas meu projeto tem milhares de classes e eu não sei onde eu esqueci de fazer isso. Ou, ainda, eu uso um framework O/RM ou JDBC que deveria resolver este problema pra mim. Bem, nesse caso, você tem como descobrir qual a instrução SQL do(s) cursor(es) que está(ão) abertos. Primeiro, você precisa do SID das sessões abertas pela sua aplicação, que você pode obter assim:
select o.sid, osuser, machine,
count(*) num_curs
from v$open_cursor o, v$session s
where o.sid=s.sid
group by o.sid, osuser, machine
order by num_curs;
Se estiver usando connection pool e um máquina só, pode-se usar estes dados para filtrar os resultados:
select o.sid, osuser, machine,
count(*) num_curs
from v$open_cursor o, v$session s
where o.sid=s.sid and
user_name = 'USUARIO_BANCO' and
machine = 'NOME_MAQUINA_APLICACAO'
group by o.sid, osuser, machine
order by o.sid;
Utilize os SIDs na seguinte query:
select q.sql_text, count(*)
from v$open_cursor o, v$sql q
where q.hash_value=o.hash_value and o.sid IN (<SIDs>)
group by q.sql_text
order by 2;
Se o framework ou servidor de aplicações que você utiliza faz cache de statements (Hibernate e JBoss fazem, por default), é normal que exista um certo número de cursores com o mesmo SQL em aberto. Suspeite daquelas instruções que tenham um número de cursores muito maior que as outras e investigue de onde elas vêm. E se sua aplicação usa PL/SQL, saiba que as instruções contidas nas suas procedures/functions são contadas individualmente como cursores. Espero ter ajudado ;-)