Håndter feil med TRY... FANG

Fullført

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).