Archive for the ‘Oracle’ Category

O infame bug de self-assignment / atribuição a si mesmo

terça-feira, março 4th, 2008

Acabo de ajudar o pessoal do cliente a resolver um erro que é comum em linguagens de programação e que me levou a refletir um pouco.

No caso, havia uma trigger PL/SQL (Oracle 9.x.y.z) em que havia uma variável qualquer, que chamaremos aqui de exemplo. Havia também uma tabela qualquer com uma coluna EXEMPLO – levando em conta que os nomes não são case-sensitive; apenas reflito como estavam sendo usados no código. Era feito um select semelhante a:

SELECT
 INTO 
FROM TABELA T
WHERE T.EXEMPLO = exemplo

E ninguém conseguia entender qual era o bendito erro. Bem, para o Oracle, o where acima é a mesma coisa que:

WHERE T.EXEMPLO = T.exemplo

também conhecido por, hmmm, true. Logo, o where não fazia nem de longe o que o desenvolvedor esperava. Foram perdidas umas 3 horas até que me contaram o problema e eu o detectei imediatamente no código – obviamente, porque também já apanhei disso antes.

A mesma coisa acontece em Java quando uma variável local tem o mesmo nome de uma variável de instância e se esquece de usar o this na atribuição. Qualquer pessoa com um pouco de experiência na linguagem já viu esse erro na vida.

Embora em Java as IDEs detectem esse tipo de erro, eu até hoje não entendo por que essas operações de self-assignment não são reportadas como erro de compilação. Alguém aí tem alguma boa razão para o compilador aceitar isso?

Resolvendo ORA-01000: maximum open cursors exceeded

terça-feira, julho 4th, 2006

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 Connections, Statements e ResultSets 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 PreparedStatements sejam usados ao invés de concatenar Strings 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 ;-)