Tratamento de Erro

No post anterior vimos a introdução para tratamento de erros de execução em queries e como utilizá-las. Continuando com o assunto, agora quero apresentar outras duas funções que irão auxiliar no desenvolvimento de suas procedures.

Throw e Raise Error.

São funções que garantem que uma procedure por exemplo, informe seu erro mesmo que haja alguma falha durante sua execução.

A primeira função que iremos entender é a função THROW que garante uma certa redundância pois força que o log de erro criado, seja executado com sucesso.

O comando throw aceita duas formas de uso: uma dentro de um try_catch block e a outra com parâmetros. Quando lançado, encerra a execução do batch ao qual se encontra.

Veja abaixo a utilização do throw na procedure de exemplo que executa uma divisão. Se der algum erro, o throw irá mostrar qual foi o problema.

PRIMEIRO EXEMPLO DE THROW MOSTRANDO SUA AÇÃO

Como houve erro de execução, temos a mensagem capturada informada.

Tenha cuidado quando for utilizar throw no tratamento de erro, pois em certas situações o SQL Server entende como uma situação ambígua.

CUIDADO COM AMBIGUIDADES PARA NÃO PASSAR O COMANDO COMO UM ‘ALIAS’ PARA CONSULTA

Nesta execução temos a seguinte situação, veja:

  1. O bloco de begin try executando a divisão e o bloco de captura(catch) para tratar os erros;
  2. Repare que o bloco de captura, ao final do select, não existe nenhuma indicação para o banco que aquela declaração acabou, logo, o throw faz parte dela.
  3. Veja no resultado em vermelho que a mensagem esta exatamente como o select e temos dois resultados, mas nenhuma mensagem de erro. Ainda que seja uma divisão por 0.

Muitos utilizam o THROW com ponto e vírgula antes do comando: ;TRHOW; 

É eficaz mas pode ser estranho. O mais aconselhável é revisar o código e garantir que a sintaxe esteja correta.

Veja outra situação que pode ser curiosa com a ambiguidade nos comandos de SQL, principalmente quando tratamos de erros.

Perceba que no catch block está explícito que o comando deveria abortar qualquer transação aberta, porém, como mais uma vez a declaração de comando não foi concluída, o engine entende que é um alias. 

MESMA FALHA, MOSTRANDO QUE PODE DEIXAR TRANSAÇÕES ABERTAS NO BANCO

NOTA:  a função @@trancount informa quantas transações existem abertas ou, que ainda não foram comitadas no banco de dados. 

Agora que vimos o problema da ambiguidade de comandos no SQL Server, veja o que acontece quando executamos a procedure acima dentro de uma transação!

EXECUTANDO A PROCEDURE EM OUTRA TRANSAÇÃO. VEJA QUE A TRANSAÇÃO PERMANECE ABERTA; O ROLLBACK NÃO PÔDE SER EFETUADO.

A procedure é a mesma do exemplo anterior e vejam que o tratamento de erros não foi executado e a transação não foi abortada.

Como o throw foi passado como um apelido para o rollback tran no bloco de captura da procedure, ele não aborta a transação e executa a divisão.

Em algumas situações pode ser que seja necessário aninhar tratamento de erro em procedures. Quando isso ocorre temos de garantir que o erro será tratado seja pelo bloco interno, seja pelo bloco externo.

Uma forma de se aninhar try..catch em procedures é:

TRY..CATCH ANINHADO. INTERNO E EXTERNO POSSUEM TRATAMENTO DE ERROS

Essa é a estrutura de uma procedure com try..catch aninhada, perceba que nos dois blocos, temos o bloco de captura. Se por um acaso o bloco interno falhar, o bloco externo age retornando o erro.

Perceba que a mensagem retornada foi a mensagem que configuramos para o bloco interno. 

ERRO CAPTURADO PELO BLOCO INTERNO

Alterando a procedure e passando o select para o bloco externo, veja o que ocorre.

PREPARANDO A PROCEDURE PARA UTILIZAR O ERRO EXTERNO

Procedure alterada, e agora vamos executá-la.

RESULTADO DA EXECUÇÃO DA IMAGEM ANTERIOR

Pronto!! mesmo o engine do banco ignorando o try..catch interno, temos nossa procedure amarrada para qualquer problema que possa ocorrer.

E caso se pergunte do porque não colocar throw em try interno, veja o que acontece no bloco abaixo.

ERRO QUANDO TEMOS THROW NOS DOIS BLOCOS DE TRATAMENTO

Em amarelo temos o bloco interno e vermelho o externo. Ao executar o bloco inteiro, veja o resultado.

O MESMO RESULTADO SENDO REPETIDO POR CONTA DAS DUAS CLÁUSULAS THROW NO BLOCO DE COMANDO.

Veja que houve erro nos dois blocos. Isso aconteceu devido:

  1. O primeiro try que possui o comando print retorna erro por tentar converter implicitamente texto em número.
  2. Quando o comando retorna erro, vai para o catch e recebe o motivo do erro e o comando throw envia o erro adiante. E é aqui que está o problema
  3. Como essa mensagem de erro passa para o próximo bloco, e o catch block externo irá tratar o erro, ele usa o mesmo comando de print do bloco interno, tornando a mensagem de erro redundante.
  4. Quando isso ocorre, temos o mesmo tipo de mensagem que obtivemos no bloco interno mais a falha de conversão do último bloco. É como se o SQL Server lançasse no log o mesmo erro duas vezes para depois executar o último tratamento.

Por este motivo, devemos evitar o uso de throw em blocos internos de tratamentos aninhados.

E veja que quando comentamos ou simplesmente apagamos o throw do bloco de comando, a execução ocorre normalmente.

USER-DEFINED ERROR: THROW E RAISERROR COM PARÂMETROS.

Antes de entrar no assunto em si, preciso adicionar uma breve explicação sobre raiserror. Este é um comando que precede o throw com uma gama maior de comandos e personalizações para saída de erros. 

O raiserror é o que mais utiliza o conceito de user-defined error (U.D.E) quando falamos deste assunto. 

Ambos podem vir tanto no catch block usual ou até mesmo fora dele. Vai do gosto.

Os parâmetros que o comando aceita são: Error_number, Message e State e podem ter como input valores constantes ou variáveis.

Veja aqui um exemplo básico de um erro definido pelo usuário. 

THROW COM PARÂMETROS

Um ponto importante a se mencionar é que erros definidos não aceitam no parâmetro de Error_number valores abaixo de 50.000. Veja a mensagem sublinhada em azul na imagem acima.

A melhor maneira de se utilizar textos personalizados dentro dos parâmetros de mensagem do throw é com variável. Veja.

THROW UTILIZANDO VARIÁVEL

Observe este exemplo.

COMPORTAMENTO DO THROW UTILIZANDO PARÂMETROS EM TRANSAÇÕES

Se repararmos em qual mensagem foi exibida, percebemos que foi a segunda mensagem, que está em outra transação. Esse tipo de acontecimento é ocasionado porque o throw costuma agir de duas formas dentro de um comando.

  • Ou ele finaliza a batch de execução (como aconteceu no exemplo e quando vem logo após um end try)
  • Ou ele pula diretamente pro catch (quando se encontra dentro de  try block)

Por isso que a segunda mensagem foi exibida, pela primeira batch ter sido abortada.

Veja neste exemplo com maior detalhe. 

NOVO TESTE, DESTA VEZ, FECHANDO AS TRANSAÇÕES PENDENTES

Como a opção de xact_abort estava off no primeiro exemplo, a transação se mantém aberta por ter sido uma batch separada de todo o bloco. Veja que antes do throw, há um ponto e vírgula que demarca o fim daquela transação e pelo fato do throw vir logo após, ela permanece aberta. 

Passando xact_abort como on, o resultado é diferente.

RAISERROR

Ainda que seja ‘antigo’ e anterior ao throw, há bastante utilidade com a função raiserror por possibilitar o uso de opções que não são utilizáveis no throw.

Os parâmetros de mensagem são: Error_message, Severity e State.

Esta imagem possuí um atributo alt vazio; O nome do arquivo é nAzZQR2quvx7b_wF0UNRlk1o4ms8Cv5g3GF4dJ140tZIRhD6NU1SkEFQ_d2WlTQ_YO54_rCJ4OB9RpwW83oYax1fsevguoNsdkYHEjxHCF-Mw5ELO6rtS-Hkzwuu34M1fPLQ2OBY
EXEMPLO BÁSICO DE RAISERRROR

Erros com severidade abaixo de 10 não são capturáveis por nenhuma função com tratamento de erro. Já os erros entre 11 e 19 são tratáveis por qualquer função e os que estão entre 20 e 25 encerram a conexão automaticamente. São severos.

Para utilizar erros com severidade maior que 19, é obrigatório a declaração da opção with log por se tratar de erros de alta severidade e o SQL Server encerrar automaticamente a conexão.

Veja abaixo. Para reconectar, basta executar um select ou qualquer outro comando.

RAISERROR COM SEVERIDADE MAIOR QUE 20 – FECHANDO A CONEXÃO

E quando utilizamos a opção no wait, o que estamos falando para o banco é que ele não precisa passar as informações pelo buffer ou esperar um pequeno delay. Veja:

OPÇÃO DE UTILIZAR RAISERROR COM NOWAIT

Execute de uma única vez, todo o bloco circulado em amarelo. Verá que a primeira mensagem aparece por conta do with nowait, mesmo com o delay de 10 segundos.

E tirando o with nowait, veja que a execução muda. O SQL Server espera 10 segundos para mostrar as duas mensagens de erro

RESULTADO DO RAISERROR SEM NOWAIT

Vendo o resultado final da query, percebemos que demorou 10 segundos para que o resultado fosse mostrado. Peço que quando forem executar, que seja os dois blocos ao mesmo tempo.

CONCLUSÃO

Este foi o segundo post sobre tratamento de erros com T-SQL no banco de dados. Aqui aprofundamos em mais funções e mais exemplos de como podemos utilizar esta ferramenta para evitar situações indesejadas quando estamos executando alguma transação.

Este recurso é muito utilizado, inclusive como boa prática quando estamos tratando de transações e Stored Procedures com múltiplas transações dentro do bloco de comando.

O próximo post será a conclusão do assunto e irei aprofundar ainda mais nele!

Link para download: aqui e aqui

Espero que gostem, saúde!