Tratamento de Exceções. Parte 2

Essa é a parte 2 de uma série de 3 posts (ou 4, quem sabe), onde eu quero falar sobre as formas de realizar o tratamento de exceções no SQL Server.

Se você caiu aqui por acaso, sugiro antes dar uma olhada na parte 1 da série, onde eu trato de alguns detalhes no que diz respeito à forma que o SQL Server realiza o encerramento de processos, nos casos de erro.

Hoje irei falar sobre como realizar o tratamento de exceções, via TRY / CATCH.

Estrutura:

Simples, não? 🙂
O objetivo: Fazer com que (quase) todo e qualquer erro de execução que esteja dentro do TRY, seja direcionado para o CATCH (o “quase” é porque isso acontece desde que a exceção tenha uma severidade maior que 10 ou que não aborte a conexão do usuário).
Caso o bloco em execução dentro do TRY não gere nenhuma exceção, o CATCH simplesmente não é executado e o que existir após o CATCH é executado normalmente.
Se o CATCH é a última linha sendo executada, o retorno vai para quem o chamou, seja uma procedure aninhada ou o próprio SSMS, por exemplo. Veja abaixo, como o CATCH não é executado caso não ocorra erro na execução:

Sem exceção...
Sem exceção…

Agora, deixe-me forçar um erro de divisão por zero e veja o que acontece:

Agora sim, entrou no CATCH.
Agora sim, entrou no CATCH.

Repare que eu forcei o mesmo erro duas vezes. A primeira, fora do TRY, para mostrar o erro que é gerado, e a segunda, mostrando que o erro não é exibido. Ao invés disso, é gerado como resultado o que está dentro do CATCH. Note também que dentro do TRY existia a execução de 3 statements, porém apenas o primeiro PRINT foi realizado.
No post anterior, eu comentei a respeito dos erros que podem ser tratados pelo usuário, onde o bloco continua a execução do batch. Porém, quando o bloco estiver dentro de um TRY, assim que um erro surgir, o fluxo será direcionado diretamente para o CATCH e os statements restantes serão ignorados. Veja:

Abortando todo o Batch
Abortando todo o bloco do TRY

Caso você queira que, no caso de uma exceção, nenhum registro inserido ou alterado seja commitado no banco, você deve trabalhar com transações:

Está faltando algo ainda...
Está faltando algo ainda…

Veja que, mesmo colocando o bloco dentro de uma transação, o registro com código 2 foi inserido. Por que?

Se eu abrir uma nova conexão e tentar fazer um select simples, vou ficar com a janela travada, em lock, pois eu abri uma transação mas o commit não foi realizado, uma vez que a exceção foi gerada antes de chegar no commit (não vou entrar no mérito dos níveis de isolamento e hints aqui). Precisamos, portanto, colocar dentro do CATCH um ROLLBACK, para que a transação seja encerrada, os dados não sejam efetivados no banco e um próximo select na tabela rode normalmente.
O CATCH deve ficar da seguinte forma:

Assim não preciso me preocupar com locks, certo? É, não é bem assim.
Nem todo o bloco dentro do TRY estará sempre dentro de uma transação. Veja o que acontece se depois do COMMIT eu forçar um erro.

TryCatch5
Opa… ROLLBACK do que?

Ou seja, eu preciso, antes de fazer o ROLLBACK, validar se existe alguma transação não efetivada no banco para encerrá-lo. Para isso, devo mudar o CATCH para:

Agora sim, como precisamos!
Agora sim, como precisamos!

Estamos quase chegando em um template para tratamento de exceções, mas ainda está faltando uma coisa para encerrar esse post: Capturar o erro que está sendo executado.

No SQL Server existem algumas funções de erro que retornam todas as situações já expostas (clique no link para acessar a referência no BOL): número, severidade, estado, procedure, linha e mensagem:

Porém, é imporante lembrar que estas funções retornam um valor apenas quando estão dentro do CATCH. Se executar diretamente, mesmo após um erro, será retornado nulo:

Não terá retorno
Não terá retorno
Agora sim, com o erro.
Agora sim, com o erro.

Bom o que estará no select, é a regra de negócio que ditará as regras.

No próximo post, pretendo falar sobre o RAISERROR e o THROW. Suas diferenças e semelhanças.

Abraço!

Deixe uma resposta