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.
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.
Nesta execução temos a seguinte situação, veja:
- O bloco de begin try executando a divisão e o bloco de captura(catch) para tratar os erros;
- 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.
- 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.
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!
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 é:
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.
Alterando a procedure e passando o select para o bloco externo, veja o que ocorre.
Procedure alterada, e agora vamos executá-la.
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.
Em amarelo temos o bloco interno e vermelho o externo. Ao executar o bloco inteiro, veja o resultado.
Veja que houve erro nos dois blocos. Isso aconteceu devido:
- O primeiro try que possui o comando print retorna erro por tentar converter implicitamente texto em número.
- 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.
- 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.
- 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.
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.
Observe este exemplo.
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.
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.
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.
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:
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
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!