Manipular erros com TRY...CATCH
Os aplicativos de banco de dados de produção devem lidar com situações inesperadas normalmente. Divisão por zero, violações de restrição, tempos limite de conexão e dados inválidos podem causar erros. Erros sem tratamento resultam em mensagens de erro não claras, transações incompletas ou falhas de aplicativo. O tratamento de erros adequado garante que o código T-SQL falhe de forma previsível e forneça comentários significativos.
As operações de banco de dados interagem simultaneamente com vários usuários, sistemas externos e entradas de dados imprevisíveis. Ao contrário do código do aplicativo que pode se recuperar de uma operação com falha ao tentar novamente, os erros de banco de dados podem deixar os dados em um estado inconsistente, com algumas linhas inseridas e outras não, ou com bloqueios mantidos indefinidamente. O tratamento de erros transforma esses modos de falha caóticos em respostas controladas e previsíveis.
O tratamento de erros bem projetado melhora seu código de várias maneiras:
Proteção de integridade de dados: quando uma operação falha de maneira parcial, o tratamento de erros adequado garante que todas as alterações sejam confirmadas em conjunto ou nenhuma delas persista. Sem isso, um processo de várias etapas pode deixar seu banco de dados com registros órfãos, totais incompatíveis ou relações interrompidas.
Eficiência de depuração: capturar detalhes de erro como o número da linha, o nome do procedimento e a mensagem de erro específica torna a solução de problemas mais rápida. Em vez de pesquisar falhas vagas nos logs, você pode identificar exatamente onde um erro ocorreu e por quê.
Experiência do usuário: os aplicativos podem exibir mensagens significativas como "A ID do produto não existe" em vez de erros de banco de dados enigmáticos. Isso ajuda os usuários a entender o que deu errado e como corrigi-lo.
Visibilidade operacional: erros de registro em log em uma tabela dedicada criam uma trilha de auditoria que ajuda a identificar padrões, como violações de restrição recorrentes que indicam bugs ou erros de tempo limite que sugerem problemas de desempenho.
Degradação normal: quando uma operação falha, o tratamento de erros permite que o restante do código continue ou execute uma ação alternativa, em vez de travar todo o lote ou procedimento armazenado.
Implementar o tratamento de erros do T-SQL
O T-SQL fornece tratamento de erros estruturados por meio de TRY...CATCH blocos, semelhante ao tratamento de exceções em outras linguagens de programação. Quando ocorre um erro no bloco TRY, a execução é transferida para o bloco CATCH, onde você pode tratar o erro adequadamente.
BEGIN TRY
-- TRY block contains code that might cause an error
-- If an error occurs here, execution jumps to the CATCH block
SELECT 1/0; -- This causes a division by zero error
END TRY
BEGIN CATCH
-- CATCH block handles the error
-- This code runs only if an error occurred in the TRY block
PRINT 'An error occurred';
END CATCH;
Sem tratamento de erros, o mesmo código terminaria com uma mensagem de erro:
SELECT 1/0; -- Msg 8134: Divide by zero error encountered
Observação
TRY...CATCH não é possível detectar todos os erros. Erros de compilação (erros de sintaxe, objetos ausentes) e erros com gravidade 20 ou superior que fecham a conexão não podem ser capturados na mesma sessão.
Recuperar informações de erro
Dentro do bloco, o CATCH SQL Server fornece as seguintes funções para recuperar detalhes sobre o erro que ocorreu:
| Função | DESCRIÇÃO |
|---|---|
ERROR_NUMBER() |
Retorna o número de erro |
ERROR_MESSAGE() |
Retorna o texto completo da mensagem de erro |
ERROR_SEVERITY() |
Retorna a gravidade do erro (0-25) |
ERROR_STATE() |
Retorna o número de estado do erro |
ERROR_LINE() |
Retorna o número da linha em que o erro ocorreu |
ERROR_PROCEDURE() |
Retorna o nome do procedimento armazenado ou gatilho |
O exemplo a seguir demonstra como capturar detalhes do erro e registrá-los em uma tabela para análise posterior:
BEGIN TRY
-- Attempt an operation that might fail
INSERT INTO SalesLT.Customer (CustomerID, FirstName, LastName)
VALUES (1, 'Test', 'Customer'); -- Duplicate key causes error
END TRY
BEGIN CATCH
-- Log error details to a table using the ERROR_* functions
INSERT INTO ErrorLog (
ErrorTime,
ErrorNumber,
ErrorSeverity,
ErrorState,
ErrorProcedure,
ErrorLine,
ErrorMessage
)
VALUES (
GETDATE(),
ERROR_NUMBER(), -- The error number (e.g., 2627 for duplicate key)
ERROR_SEVERITY(), -- Severity level (0-25)
ERROR_STATE(), -- Error state for debugging
ISNULL(ERROR_PROCEDURE(), 'Ad hoc query'), -- NULL if not in a procedure
ERROR_LINE(), -- Line number where error occurred
ERROR_MESSAGE() -- Full error message text
);
-- Re-raise the error to the calling application
THROW;
END CATCH;
Dica
Sempre registre erros antes de repetir. Depois de usar THROW ou RAISERROR, as funções de erro retornarão NULL se forem chamadas novamente.
Manipular transações com TRY...CATCH
Quando ocorrem erros em transações, você deve reverter explicitamente o trabalho pendente. A @@TRANCOUNT função informa se uma transação está ativa:
BEGIN TRY
-- Start a transaction to group multiple operations
BEGIN TRANSACTION;
-- First operation: update product prices
UPDATE SalesLT.Product
SET ListPrice = ListPrice * 1.05
WHERE ProductCategoryID = 5;
-- Second operation: update order totals
-- If this fails, we want to undo the first update too
UPDATE SalesLT.SalesOrderHeader
SET TotalDue = TotalDue * 1.05
WHERE CustomerID = 12345;
-- Both operations succeeded, make changes permanent
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Check if a transaction is still active before rolling back
-- Some errors auto-rollback, so @@TRANCOUNT might be 0
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION; -- Undo all changes from this transaction
-- Re-raise the error so the caller knows something failed
THROW;
END CATCH;
A @@TRANCOUNT verificação é importante porque:
- Um erro pode ocorrer antes de
BEGIN TRANSACTION - Alguns erros revertem automaticamente a transação antes de chegar ao
CATCH - A tentativa de reversão sem uma transação ativa causa outro erro
Importante
Sempre verifique @@TRANCOUNT antes de chamar ROLLBACK TRANSACTION dentro de um bloco CATCH. Isso impede o erro "A solicitação ROLLBACK TRANSACTION não tem nenhuma TRANSAÇÃO BEGIN correspondente".
Gerar erros personalizados com THROW
A THROW instrução gera uma exceção com um número de erro personalizado e uma mensagem. Use-o para sinalizar condições de erro específicas do aplicativo:
CREATE PROCEDURE ProcessOrder
@OrderID INT,
@Quantity INT
AS
BEGIN
BEGIN TRY
-- Validate input and raise custom errors for invalid data
IF @Quantity <= 0
THROW 50001, 'Quantity must be greater than zero.', 1;
IF NOT EXISTS (SELECT 1 FROM Orders WHERE OrderID = @OrderID)
THROW 50002, 'Order not found.', 1;
-- Process the order
UPDATE Orders
SET Quantity = @Quantity
WHERE OrderID = @OrderID;
END TRY
BEGIN CATCH
-- Log the error before reraising
EXEC LogError;
-- THROW without parameters reraises the current error
THROW;
END CATCH;
END;
Os números de erro personalizados para erros definidos pelo usuário devem ser 50000 ou superiores. O parâmetro de estado (1 nos exemplos) é um valor definido pelo usuário entre 1 e 255 que pode ajudar a identificar onde o erro foi gerado.
Usar RAISERROR para mensagens formatadas
RAISERROR fornece mais opções de formatação do que THROW, incluindo substituição de parâmetro no estilo printf. Incluir valores de runtime em mensagens de erro facilita a depuração porque você pode ver exatamente quais dados causaram a falha sem pesquisar logs ou reproduzir o problema:
DECLARE @ProductName NVARCHAR(100) = 'Widget Pro';
DECLARE @CurrentStock INT = 5;
DECLARE @RequestedQty INT = 10;
IF @CurrentStock < @RequestedQty
BEGIN
RAISERROR(
'Insufficient stock for product "%s". Available: %d, Requested: %d',
16, -- Severity
1, -- State
@ProductName,
@CurrentStock,
@RequestedQty
);
END;
Observação
THROW é a abordagem recomendada para código novo porque é mais simples e sempre inclui um stack trace. Utilize RAISERROR quando precisar de mensagens formatadas ou compatibilidade com padrões existentes de tratamento de erros.
Implementar tratamento de erros aninhados
Os procedimentos armazenados que chamam outros procedimentos precisam de tratamento de erro coordenado. Cada nível deve lidar com sua própria limpeza e propagar erros adequadamente:
CREATE PROCEDURE OuterProcedure
AS
BEGIN
BEGIN TRY
-- Outer procedure owns the transaction
BEGIN TRANSACTION;
-- First operation in the outer procedure
UPDATE SomeTable SET Column1 = 'Value';
-- Call nested procedure - if it fails, error propagates here
EXEC InnerProcedure;
-- All operations succeeded, commit the transaction
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Outer procedure handles rollback for all nested calls
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- Propagate error to the application
THROW;
END CATCH;
END;
GO
CREATE PROCEDURE InnerProcedure
AS
BEGIN
BEGIN TRY
-- Inner procedure does its work within the outer's transaction
UPDATE AnotherTable SET Column2 = 'Value';
END TRY
BEGIN CATCH
-- Don't rollback here - let the outer procedure handle it
-- This keeps transaction management in one place
THROW; -- Re-raise error to outer procedure
END CATCH;
END;
Usar XACT_ABORT para reversão automática
Você pode definir XACT_ABORT ON para fazer com que o SQL Server reverta automaticamente a transação quando ocorrer qualquer erro, mesmo sem TRY...CATCH isso:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
UPDATE Table1 SET Col1 = 'A';
UPDATE Table2 SET Col2 = 'B'; -- If this fails, entire transaction rolls back
UPDATE Table3 SET Col3 = 'C';
COMMIT TRANSACTION;
A combinação XACT_ABORT com TRY...CATCH oferece os benefícios de ambas as abordagens: XACT_ABORT garante a reversão imediata para qualquer erro, enquanto TRY...CATCH permite que você registre detalhes de erro e execute a limpeza personalizada antes que o erro se propague:
-- XACT_ABORT ON ensures automatic rollback on any error
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Execute multiple procedures as a single unit of work
EXEC Procedure1; -- If any of these fail...
EXEC Procedure2; -- ...XACT_ABORT automatically rolls back...
EXEC Procedure3; -- ...and jumps to the CATCH block
-- All succeeded, commit the changes
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- With XACT_ABORT ON, the transaction is usually already rolled back
-- This check handles edge cases where it might still be active
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- Log the error details before re-raising
EXEC LogError;
-- Let the caller know an error occurred
THROW;
END CATCH;
Dica
Usar SET XACT_ABORT ON é uma prática recomendada para procedimentos armazenados, especialmente aqueles que abrangem várias operações. Ele garante um comportamento consistente, independentemente do erro específico que ocorre.
Para obter mais informações sobre tratamento de erros, consulte TRY... CATCH (Transact-SQL) e THROW (Transact-SQL).