Håndter feil med TRY... FANG
Produksjonsdatabaseapplikasjoner må håndtere uventede situasjoner på en elegant måte. Divisjon med null, brudd på begrensninger, tilkoblingstidsavbrudd og ugyldige data kan alle forårsake feil. Uhåndterte feil fører til uklare feilmeldinger, ufullstendige transaksjoner eller applikasjonskrasj. Riktig feilhåndtering sikrer at T-SQL-koden din feiler forutsigbart og gir meningsfull tilbakemelding.
Databaseoperasjoner samhandler med flere brukere, eksterne systemer og uforutsigbare datainndata samtidig. I motsetning til applikasjonskode som kan gjenopprette en mislykket operasjon ved å prøve på nytt, kan databasefeil etterlate data i en inkonsekvent tilstand, med noen rader satt inn og andre ikke, eller med låser holdt på ubestemt tid. Feilhåndtering forvandler disse kaotiske feilmodusene til kontrollerte, forutsigbare responser.
Godt designet feilhåndtering forbedrer koden din på flere måter:
Beskyttelse av dataintegritet: Når en operasjon feiler underveis, sikrer korrekt feilhåndtering at enten alle endringer forpliktes sammen eller at ingen av dem vedvarer. Uten dette kan en flertrinnsprosess føre til at databasen din sitter igjen med foreldreløse poster, mismatchede totalsummer eller ødelagte relasjoner.
Feilsøkingseffektivitet: Å fange feildetaljer som linjenummer, prosedyrenavn og spesifikk feilmelding gjør feilsøking raskere. I stedet for å lete gjennom logger etter vage feil, kan du finne nøyaktig hvor og hvorfor en feil oppstod.
Brukeropplevelse: Applikasjoner kan vise meningsfulle meldinger som «Produkt-ID-en eksisterer ikke» i stedet for kryptiske databasefeil. Dette hjelper brukerne å forstå hva som gikk galt og hvordan det kan fikses.
Operasjonell synlighet: Logging av feil til en dedikert tabell skaper en revisjonsspor som hjelper til med å identifisere mønstre, som gjentatte begrensningsbrudd som indikerer en feil eller tidsavbrudd som tyder på ytelsesproblemer.
Grasiell degradering: Når en operasjon feiler, lar feilhåndtering resten av koden fortsette eller ta alternative tiltak, i stedet for at hele batchen eller lagret prosedyre krasjer.
Implementer T-SQL-feilhåndtering
T-SQL gir strukturert feilhåndtering gjennom TRY...CATCH blokker, lik unntakshåndtering i andre programmeringsspråk. Når en feil oppstår i blokken TRY , overføres utførelsen til blokken CATCH hvor du kan håndtere feilen på riktig måte:
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;
Uten feilhåndtering ville den samme koden avsluttes med en feilmelding:
SELECT 1/0; -- Msg 8134: Divide by zero error encountered
Bemerkning
TRY...CATCH Man kan ikke fange alle feil. Kompilasjonsfeil (syntaksfeil, manglende objekter) og feil med alvorlighetsgrad 20 eller høyere som lukker forbindelsen, kan ikke fanges i samme økt.
Hente feilinformasjon
Innenfor blokken CATCH tilbyr SQL Server følgende funksjoner for å hente detaljer om feilen som oppstod:
| Function | Beskrivelse |
|---|---|
ERROR_NUMBER() |
Returnerer feilnummeret |
ERROR_MESSAGE() |
Returnerer hele feilmeldingsteksten |
ERROR_SEVERITY() |
Returnerer feilens alvorlighetsgrad (0-25) |
ERROR_STATE() |
Returnerer feilstatusnummeret |
ERROR_LINE() |
Returnerer linjenummeret der feilen oppstod |
ERROR_PROCEDURE() |
Returnerer navnet på den lagrede prosedyren eller triggeren |
Følgende eksempel demonstrerer hvordan man kan fange feildetaljer og logge dem i en tabell for senere analyse:
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
Logg alltid feil før du tar dem opp igjen. Når du bruker THROW eller RAISERROR, returnerer NULL feilfunksjonene hvis de kalles igjen.
Håndter transaksjoner med TRY... FANG
Når feil oppstår i transaksjoner, må du eksplisitt rulle tilbake uforpliktet arbeid. Funksjonen @@TRANCOUNT forteller deg om en transaksjon er 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;
Sjekken @@TRANCOUNT er viktig fordi:
- En feil kan oppstå før
BEGIN TRANSACTION - Noen feil ruller automatisk tilbake transaksjonen før de når frem
CATCH - Forsøk på tilbakerulling uten en aktiv transaksjon forårsaker en ny feil
Viktig!
Sjekk @@TRANCOUNT alltid før du ringer ROLLBACK TRANSACTION inn en CATCH blokk. Dette forhindrer feilen "ROLLBACK TRANSACTION request has no corresponding START TRANSACTION."
Oppdrag tilpassede feil med THROW
Setningen THROW gir et unntak med et egendefinert feilnummer og melding. Bruk det til å signalisere applikasjonsspesifikke feiltilstander:
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;
Egendefinerte feilnumre for brukerdefinerte feil må være 50000 eller høyere. Tilstandsparameteren (1 i eksemplene) er en brukerdefinert verdi mellom 1 og 255 som kan hjelpe til med å identifisere hvor feilen ble oppløst.
Bruk RAISERROR for formaterte meldinger
RAISERROR gir flere formateringsmuligheter enn THROW, inkludert PrintF-stil parametersubstitusjon. Å inkludere kjøretidsverdier i feilmeldinger gjør feilsøking enklere fordi du kan se nøyaktig hvilke data som forårsaket feilen uten å måtte grave gjennom logger eller gjenskape 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;
Bemerkning
THROW er den anbefalte tilnærmingen for ny kode fordi det er enklere og alltid inkluderer en stakksporing. Bruk RAISERROR når du trenger formaterte meldinger eller kompatibilitet med eksisterende feilhåndteringsmønstre.
Implementer nestet feilhåndtering
Lagrede prosedyrer som kaller andre prosedyrer krever koordinert feilhåndtering. Hvert nivå bør håndtere sin egen opprydding og overføre feil på riktig måte:
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;
Bruk XACT_ABORT for automatisk tilbakerulling
Du kan sette XACT_ABORT ON slik at SQL Server automatisk ruller tilbake transaksjonen når en feil oppstår, selv uten TRY...CATCH slik:
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;
Kombinasjon XACT_ABORT med TRY...CATCH gir deg fordelene med begge tilnærmingene: XACT_ABORT garanterer umiddelbar tilbakerulling for enhver feil, samtidig som TRY...CATCH du kan logge feildetaljer og utføre egendefinert opprydding før feilen sprer seg:
-- 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
SET XACT_ABORT ON Bruk er beste praksis for lagrede prosedyrer, spesielt de som spenner over flere operasjoner. Det sikrer konsistent oppførsel uavhengig av den spesifikke feilen som oppstår.
For mer informasjon om feilhåndtering, se TRY... FANG (Transact-SQL ) og KAST (Transact-SQL).