Hantera fel med TRY...CATCH
Program för produktionsdatabaser måste hantera oväntade situationer på ett korrekt sätt. Division efter noll, begränsningsöverträdelser, tidsgränser för anslutning och ogiltiga data kan alla orsaka fel. Ohanterade fel resulterar i oklara felmeddelanden, ofullständiga transaktioner eller programkrascher. Korrekt felhantering säkerställer att T-SQL-koden misslyckas förutsägbart och ger meningsfull feedback.
Databasåtgärder interagerar med flera användare, externa system och oförutsägbara dataindata samtidigt. Till skillnad från programkod som kan återställas från en misslyckad åtgärd genom att försöka igen, kan databasfel lämna data i ett inkonsekvent tillstånd, med vissa rader infogade och andra inte, eller med lås som hålls på obestämd tid. Felhantering omvandlar dessa kaotiska fellägen till kontrollerade, förutsägbara svar.
Väl utformad felhantering förbättrar koden på flera sätt:
Datas integritetsskydd: När en åtgärd misslyckas delvis säkerställer korrekt felhantering att antingen alla ändringar bekräftas tillsammans eller att ingen av dem kvarstår. Utan detta kan en process i flera steg lämna databasen med överblivna poster, felmatchade summor eller brutna relationer.
Felsökningseffektivitet: Felsökningen går snabbare genom att samla in felinformation som radnummer, procedurnamn och specifika felmeddelanden. I stället för att söka igenom loggar efter vaga fel kan du hitta exakt var och varför ett fel uppstod.
Användarupplevelse: Program kan visa meningsfulla meddelanden som "Produkt-ID:t finns inte" i stället för kryptiska databasfel. Detta hjälper användarna att förstå vad som gick fel och hur de åtgärdar det.
Driftssynlighet: Loggningsfel i en dedikerad tabell skapar en spårningslogg som hjälper till att identifiera mönster, till exempel återkommande begränsningsöverträdelser som indikerar ett fel eller tidsgränsfel som tyder på prestandaproblem.
Graciös försämring: När en åtgärd misslyckas kan du med felhanteringen fortsätta med koden eller vidta alternativa åtgärder i stället för att krascha hela batchen eller den lagrade proceduren.
Implementera T-SQL-felhantering
T-SQL tillhandahåller strukturerad felhantering via TRY...CATCH block, ungefär som undantagshantering på andra programmeringsspråk. När ett fel inträffar i TRY blocket överförs körningen till blocket CATCH där du kan hantera felet på rätt sätt:
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;
Utan felhantering skulle samma kod avslutas med ett felmeddelande:
SELECT 1/0; -- Msg 8134: Divide by zero error encountered
Anmärkning
TRY...CATCH kan inte fånga upp alla fel. Kompileringsfel (syntaxfel, saknade objekt) och fel med allvarlighetsgrad 20 eller högre som stänger anslutningen kan inte fångas inom samma session.
Hämta felinformation
CATCH I blocket tillhandahåller SQL Server följande funktioner för att hämta information om felet som inträffade:
| Funktion | Description |
|---|---|
ERROR_NUMBER() |
Returnerar felnumret |
ERROR_MESSAGE() |
Returnerar den fullständiga felmeddelandetexten |
ERROR_SEVERITY() |
Returnerar allvarlighetsgraden för felet (0–25) |
ERROR_STATE() |
Returnerar feltillståndsnumret |
ERROR_LINE() |
Returnerar radnumret där felet inträffade |
ERROR_PROCEDURE() |
Returnerar namnet på den lagrade proceduren eller utlösaren |
I följande exempel visas hur du samlar in felinformation och loggar dem till en tabell för senare analys:
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;
Tips/Råd
Logga alltid fel innan du gör om dem. När du använder THROW eller RAISERRORreturnerar NULL felfunktionerna om de anropas igen.
Hantera transaktioner med TRY...CATCH
När fel inträffar inom transaktioner måste du uttryckligen återställa obekräftat arbete. Funktionen @@TRANCOUNT anger om en transaktion är aktiv:
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;
Kontrollen @@TRANCOUNT är viktig eftersom:
- Ett fel kan inträffa innan
BEGIN TRANSACTION - Vissa fel återställer automatiskt transaktionen innan den når
CATCH - Försök att återställa utan en aktiv transaktion orsakar ett annat fel
Viktigt!
Kontrollera @@TRANCOUNT alltid innan du anropar ROLLBACK TRANSACTION i ett CATCH block. Detta förhindrar felet "ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION."
Skapa anpassade fel med THROW
Instruktionen THROW genererar ett undantag med ett anpassat felnummer och meddelande. Använd den för att signalera programspecifika feltillstånd:
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;
Anpassade felnummer för användardefinierade fel måste vara 50000 eller högre. Tillståndsparametern (1 i exemplen) är ett användardefinierat värde mellan 1 och 255 som kan hjälpa dig att identifiera var felet uppstod.
Använd RAISERROR för formaterade meddelanden
RAISERROR innehåller fler formateringsalternativ än THROW, inklusive parameterersättning i printf-stil. Om du inkluderar körningsvärden i felmeddelanden blir det enklare att felsöka eftersom du kan se exakt vilka data som orsakade felet utan att gräva igenom loggar eller återskapa problemet:
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;
Anmärkning
THROW är den rekommenderade metoden för ny kod eftersom den är enklare och alltid innehåller en stackspårning. Använd RAISERROR när du behöver formaterade meddelanden eller kompatibilitet med befintliga mönster för felhantering.
Implementera kapslad felhantering
Lagrade procedurer som anropar andra procedurer behöver samordnad felhantering. Varje nivå bör hantera sin egen rensning och sprida fel på rätt sätt:
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;
Använd XACT_ABORT för automatisk återställning
Du kan ställa in XACT_ABORT ON så att SQL Server automatiskt återställer transaktionen när ett fel inträffar, även utan TRY...CATCH att göra så här:
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;
Genom att kombinera XACT_ABORT med TRY...CATCH får du fördelarna med båda metoderna: XACT_ABORT garanterar omedelbar återställning vid eventuella fel, medan TRY...CATCH gör att du kan logga felinformation och utföra anpassad rensning innan felet sprids:
-- 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;
Tips/Råd
Att använda SET XACT_ABORT ON är bästa praxis för lagrade procedurer, särskilt de som omfattar flera åtgärder. Det säkerställer konsekvent beteende oavsett det specifika fel som inträffar.
Mer information om felhantering finns i TRY... CATCH (Transact-SQL) och THROW (Transact-SQL).