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:
1 2 3 4 5 6 |
BEGIN TRY <Bloco de comandos> END TRY BEGIN CATCH <Bloco de comandos> END CATCH |
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:
1 2 3 4 5 6 7 8 |
PRINT 'Antes do TRY' BEGIN TRY PRINT 'Primeiro item no TRY' END TRY BEGIN CATCH PRINT 'Primeiro item no CATCH' END CATCH PRINT |
Agora, deixe-me forçar um erro de divisão por zero e veja o que acontece:
1 2 3 4 5 6 7 8 9 10 11 12 |
set nocount on select 1/0 PRINT 'Antes do TRY' BEGIN TRY PRINT 'Primeiro item no TRY' select 1/0 PRINT 'Segundo item no TRY' END TRY BEGIN CATCH PRINT 'Primeiro item no CATCH' END CATCH PRINT |
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:
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:
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:
1 2 3 4 |
BEGIN CATCH PRINT 'Gerou um erro' ROLLBACK END CATCH |
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.
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:
1 2 3 4 5 |
BEGIN CATCH PRINT 'Gerou um erro' IF @@TRANCOUNT > 0 ROLLBACK END CATCH |
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:
1 2 3 4 5 6 7 |
SELECT ERROR_NUMBER() AS Numero, ERROR_SEVERITY() AS Severidade, ERROR_STATE() AS Estado, ERROR_PROCEDURE() AS [Procedure], ERROR_LINE() AS Linha, ERROR_MESSAGE() AS 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:
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!