Verwenden von TRY...CATCH in Transact-SQL

Fehler im Transact-SQL-Code können mithilfe eines TRY…CATCH-Konstrukts verarbeitet werden, ähnlich den Ausnahmeverarbeitungsfunktionen in den Sprachen Microsoft Visual C++ und Microsoft Visual C#. Ein TRY…CATCH-Konstrukt besteht aus zwei Teilen: einem TRY-Block und einem CATCH-Block. Wenn in einer Transact-SQL-Anweisung innerhalb eines TRY-Blocks eine Fehlerbedingung erkannt wird, wird die Steuerung an einen CATCH-Block übergeben, wo der Fehler verarbeitet werden kann.

Nachdem der CATCH-Block die Ausnahme verarbeitet, wird die Steuerung an die erste Transact-SQL-Anweisung übertragen, die auf die END CATCH-Anweisung folgt. Falls die END CATCH-Anweisung die letzte Anweisung in einer gespeicherten Prozedur oder einem Trigger ist, wird die Steuerung an den Code zurückgegeben, der die gespeicherte Prozedur oder den Trigger aufgerufen hat. Transact-SQL-Anweisungen in dem TRY-Block, der auf die Anweisung folgt, durch die ein Fehler generiert wurde, werden nicht ausgeführt.

Wenn der TRY-Block keine Fehler aufweist, wird die Steuerung sofort nach der zugeordneten END CATCH-Anweisung an die Anweisung übergeben. Falls es sich bei der END CATCH-Anweisung um die letzte Anweisung in einer gespeicherten Prozedur oder einem Trigger handelt, wird die Steuerung an die Anweisung übergeben, die die gespeicherte Prozedur oder den Trigger ausgelöst hat.

Ein TRY-Block beginnt mit der BEGIN TRY-Anweisung und endet mit der END TRY-Anweisung. Eine oder mehrere Transact-SQL-Anweisungen können zwischen den BEGIN TRY- und END TRY-Anweisungen angegeben werden.

Ein TRY-Block muss unmittelbar von einem CATCH-Block gefolgt werden. Ein CATCH-Block beginnt mit der BEGIN CATCH-Anweisung und endet mit der END CATCH-Anweisung. In Transact-SQL ist jeder TRY-Block nur jeweils einem CATCH-Block zugeordnet.

Arbeiten mit TRY…CATCH

Berücksichtigen Sie bei der Verwendung des TRY…CATCH-Konstrukts folgende Richtlinien und Vorschläge:

  • Jedes TRY…CATCH-Konstrukt muss sich vollständig in einem einzelnen Batch, einer gespeicherten Prozedur oder einem Trigger befinden. Es ist beispielsweise nicht möglich, einen TRY-Block in einem Batch und den zugehörigen CATCH-Block in einem anderen Batch zu platzieren. Folgendes Skript würde beispielsweise einen Fehler generieren:

    BEGIN TRY
        SELECT *
            FROM sys.messages
            WHERE message_id = 21;
    END TRY
    GO
    -- The previous GO breaks the script into two batches,
    -- generating syntax errors. The script runs if this GO
    -- is removed.
    BEGIN CATCH
        SELECT ERROR_NUMBER() AS ErrorNumber;
    END CATCH;
    GO
    
  • Auf einen TRY-Block muss direkt ein CATCH-Block folgen.

  • TRY…CATCH-Konstrukte können geschachtelt werden. Das heißt, dass TRY…CATCH-Konstrukte in anderen TRY- und CATCH-Blocks platziert werden können. Wenn ein Fehler in einem geschachtelten TRY-Block auftritt, wird die Programmsteuerung an den CATCH-Block übertragen, der dem geschachtelten TRY-Block zugeordnet ist.

  • Um einen Fehler zu behandeln, der in einem bestimmten CATCH-Block auftritt, schreiben Sie einen TRY…...CATCH-Block innerhalb des angegebenen CATCH-Blockes.

  • Fehler mit einem Schweregrad von 20 oder höher, aufgrund derer die Verbindung durch Database Engine (Datenbankmodul) beendet wird, werden nicht durch den TRY…CATCH-Block behandelt. TRY…CATCH behandelt jedoch Fehler mit einem Schweregrad von 20 oder höher, wenn die Verbindung nicht beendet wird.

  • Fehler mit einem Schweregrad von 10 oder niedriger werden als Warnungen oder Informationsmeldungen angesehen und nicht von TRY…CATCH-Blocks behandelt.

  • Warnungen beenden einen Batch, auch wenn sich der Batch innerhalb des Bereichs eines TRY…CATCH-Konstrukts befindet. Dies gilt auch für Warnungen, die von Microsoft Distributed Transaction Coordinator (MS DTC) gesendet werden, wenn eine verteilte Transaktion einen Fehler erzeugt. MS DTC verwaltet verteilte Transaktionen.

    HinweisHinweis

    Wenn eine verteilte Transaktion innerhalb des Bereichs eines TRY-Blockes ausgeführt wird und ein Fehler auftritt, wird die Ausführung an den zugeordneten CATCH-Block übertragen. Die verteilte Transaktion erhält den Status, gemäß dem kein Commit für sie ausgeführt werden kann. Die Ausführung innerhalb des CATCH-Blockes kann durch den Microsoft Distributed Transaction Coordinator unterbrochen werden, der verteilte Transaktionen verwaltet. Wenn der Fehler auftritt, benachrichtigt MS DTC asynchron sämtliche Server, die an der verteilten Transaktion beteiligt sind, und beendet alle von der verteilten Transaktion betroffenen Tasks. Diese Benachrichtigung wird in Form einer Warnung gesendet, die nicht von einem TRY…CATCH-Konstrukt behandelt wird, und der Batch wird beendet. Nach Abschluss eines Batches wird von Database Engine (Datenbankmodul) für aktive Transaktionen, für die kein Commit ausgeführt werden kann, ein Rollback ausgeführt. Wenn keine Fehlermeldung gesendet wurde, als die Transaktion den nicht commitfähigen Status angenommen hat, wird bei Abschluss des Batches eine Fehlermeldung an die Clientanwendung gesendet, die besagt, dass eine nicht commitfähige Transaktion erkannt und ein Rollback für diese ausgeführt wurde. Weitere Informationen zu verteilten Transaktionen finden Sie unter Verteilte Transaktionen (Datenbankmodul).

Fehlerfunktionen

TRY…CATCH verwendet die folgenden Fehlerfunktionen, um Fehlerinformationen zu sammeln.

  • ERROR_NUMBER() gibt die Fehlernummer zurück.

  • ERROR_MESSAGE() gibt den vollständigen Text der Fehlermeldung zurück. Der Text schließt die Werte ein, die für beliebige ersetzbare Parameter angegeben sind, beispielsweise Längenangaben, Objektnamen oder Zeitangaben.

  • ERROR_SEVERITY() gibt den Fehlerschweregrad zurück.

  • ERROR_STATE() gibt die Fehlerzustandsnummer zurück.

  • ERROR_LINE() gibt die Zeilennummer innerhalb der Routine zurück, die den Fehler verursacht hat.

  • ERROR_PROCEDURE() gibt den Namen der gespeicherten Prozedur oder des Triggers mit dem Fehler zurück.

Fehlerinformationen werden mithilfe dieser Funktionen von einer beliebigen Stelle innerhalb des Bereichs des CATCH-Blocks eines TRY…CATCH-Konstrukts abgerufen. Die Fehlerfunktionen geben einen NULL-Wert zurück, wenn sie außerhalb des Bereichs eines CATCH-Blocks aufgerufen werden. Auf Fehlerfunktionen kann innerhalb einer gespeicherten Prozedur verwiesen werden. Sie können außerdem verwendet werden, um Fehlerinformationen abzurufen, wenn die gespeicherte Prozedur im CATCH-Block ausgeführt wird. Hierdurch ist es nicht mehr erforderlich, den Fehlerbehandlungscode in jedem CATCH-Block zu wiederholen. Im folgenden Codebeispiel generiert die SELECT-Anweisung im TRY-Block einen Fehler aufgrund einer Division durch 0. Der Fehler wird vom CATCH-Block behandelt, der eine gespeicherte Prozedur verwendet, um Fehlerinformationen zurückzugeben.

USE AdventureWorks2008R2;
GO

-- Verify that the stored procedure does not exist.
IF OBJECT_ID ('usp_GetErrorInfo', 'P') IS NOT NULL
    DROP PROCEDURE usp_GetErrorInfo;
GO

-- Create a procedure to retrieve error information.
CREATE PROCEDURE usp_GetErrorInfo
AS
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() as ErrorState,
        ERROR_PROCEDURE() as ErrorProcedure,
        ERROR_LINE() as ErrorLine,
        ERROR_MESSAGE() as ErrorMessage;
GO

BEGIN TRY
    -- Generate divide-by-zero error.
    SELECT 1/0;
END TRY
BEGIN CATCH
    -- Execute the error retrieval routine.
    EXECUTE usp_GetErrorInfo;
END CATCH;
GO

Kompilierfehler und Fehler beim erneuten Kompilieren auf Anweisungsebene

Es gibt zwei Fehlertypen, die nicht von TRY…CATCH behandelt werden, wenn der Fehler auf derselben Ausführungsebene wie das TRY…CATCH-Konstrukt auftritt:

  • Kompilierfehler, beispielsweise Syntaxfehler, die verhindern, dass ein Batch ausgeführt wird.

  • Fehler, die während der Neukompilierung auf Anweisungsebene auftreten, beispielsweise Fehler bei der Objektnamensauflösung, die aufgrund einer verzögerten Namensauflösung nach der Kompilierung auftreten.

Wenn der Batch, die gespeicherte Prozedur oder der Trigger, der das TRY…CATCH-Konstrukt enthält, einen dieser Fehler generiert, werden diese Fehler nicht vom TRY…CATCH-Konstrukt behandelt. Diese Fehler werden an die Anwendung oder den Batch zurückgegeben, der die Routine aufgerufen hat, durch die der Fehler generiert wurde. Im folgenden Codebeispiel wird z. B. eine SELECT-Anweisung gezeigt, die einen Syntaxfehler verursacht. Wenn dieser Code im Abfrage-Editor von SQL Server Management Studio- ausgeführt wird, wird die Ausführung nicht gestartet, da der Batch nicht kompiliert werden kann. Der Fehler wird an den Abfrage-Editor zurückgegeben und nicht von TRY…CATCH erfasst.

USE AdventureWorks2008R2;
GO

BEGIN TRY
    -- This PRINT statement will not run because the batch
    -- does not begin execution.
    PRINT N'Starting execution';

    -- This SELECT statement contains a syntax error that
    -- stops the batch from compiling successfully.
    SELECT ** FROM HumanResources.Employee;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Im Gegensatz zu dem Syntaxfehler im vorherigen Beispiel verhindert ein Fehler, der bei einer Neukompilierung auf Anweisungsebene auftritt, nicht, dass der Batch kompiliert wird. Der Batch wird jedoch beendet, sobald die Neukompilierung für die Anweisung einen Fehler generiert. Wenn ein Batch beispielsweise zwei Anweisungen aufweist und die zweite Anweisung auf eine nicht vorhandene Tabelle verweist, verursacht eine verzögerte Namensauflösung, dass der Batch erfolgreich kompiliert wird und die Ausführung gestartet wird, ohne dass die fehlende Tabelle an den Abfrageplan gebunden wird, bis die entsprechende Anweisung neu kompiliert wird. Die Batchausführung wird beendet, sobald die Anweisung erreicht wird, die auf die fehlende Tabelle verweist, und ein Fehler wird zurückgegeben. Dieser Fehlertyp kann nicht von einem TRY…CATCH-Konstrukt auf derselben Ausführungsebene behandelt werden, auf der auch der Fehler aufgetreten ist. Im folgenden Beispiel wird dieses Verhalten veranschaulicht.

USE AdventureWorks2008R2;
GO

BEGIN TRY
    -- This PRINT statement will run because the error
    -- occurs at the SELECT statement.
    PRINT N'Starting execution';

    -- This SELECT statement will generate an object name
    -- resolution error because the table does not exist.
    SELECT * FROM NonExistentTable;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Sie können TRY…CATCH für die Behandlung von Fehlern verwenden, die während der Kompilierung oder der Neukompilierung auf Anweisungsebene auftreten, wenn Sie den Code, durch den der Fehler generiert wurde, in einem separaten Batch innerhalb des TRY-Blocks ausführen. Hierfür muss der Code beispielsweise in einer gespeicherten Prozedur platziert werden, oder es muss eine dynamische Transact-SQL-Anweisung mithilfe von sp_executesql ausgeführt werden. Auf diese Weise kann TRY…CATCH den Fehler auf einer höheren Ausführungsebene erfassen als auf der Ebene, auf der der Fehler aufgetreten ist. Im folgenden Code wird z. B. eine gespeicherte Prozedur gezeigt, die einen Fehler bei der Objektnamensauflösung generiert. Der Batch, der das TRY…CATCH-Konstrukt enthält, wird auf einer höheren Ebene ausgeführt als die gespeicherte Prozedur, und der Fehler, der auf einer niedrigeren Ebene auftritt, wird erfasst.

USE AdventureWorks2008R2;
GO

-- Verify that the stored procedure does not already exist.
IF OBJECT_ID ('usp_MyError', 'P') IS NOT NULL
    DROP PROCEDURE usp_MyError;
GO

CREATE PROCEDURE usp_MyError
AS
    -- This SELECT statement will generate
    -- an object name resolution error.
    SELECT * FROM NonExistentTable;
GO

BEGIN TRY
    -- Run the stored procedure.
    EXECUTE usp_MyError;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Dies ist das Resultset.

ErrorNumber ErrorMessage
----------- ---------------------------------------
208         Invalid object name 'NonExistentTable'.

Weitere Informationen finden Sie unter Verzögerte Namensauflösung und Kompilierung sowie im Abschnitt zum erneuten Kompilieren von Ausführungsplänen unter Zwischenspeichern und Wiederverwenden von Ausführungsplänen.

Transaktionen, für die kein Commit ausgeführt werden kann

In einem TRY…CATCH-Konstrukt können Transaktionen einen Status erreichen, in dem sie geöffnet bleiben, jedoch kein Commit für sie ausgeführt werden kann. Die Transaktion kann keine Aktionen ausführen, durch die ein Schreibvorgang im Transaktionsprotokoll generiert wird, beispielsweise das Ändern von Daten oder der Versuch eines Rollbacks an einen Sicherungspunkt. In diesem Status werden die Sperren, die für die Transaktion eingerichtet wurden, jedoch beibehalten, und die Verbindung bleibt geöffnet. Die Auswirkungen der Transaktion werden erst rückgängig gemacht, wenn eine ROLLBACK-Anweisung ausgegeben wird oder wenn der Batch beendet und für die Transaktion von Database Engine (Datenbankmodul) automatisch ein Rollback ausgeführt wird. Wenn keine Fehlermeldung gesendet wurde, als die Transaktion den nicht commitfähigen Status angenommen hat, wird bei Abschluss des Batches eine Fehlermeldung an die Clientanwendung gesendet, die besagt, dass eine nicht commitfähige Transaktion erkannt und dafür ein Rollback ausgeführt wurde.

Eine Transaktion erreicht den Status, in dem kein Commit für sie ausgeführt werden kann, innerhalb eines TRY-Blocks, wenn ein Fehler auftritt, der die Transaktion andernfalls beendet hätte. Die meisten Fehler aus einer DDL-Anweisung (Data Definition Language), wie beispielsweise CREATE TABLE, oder die meisten Fehler, die auftreten, wenn SET XACT_ABORT auf ON festgelegt ist, beenden die Transaktion außerhalb eines TRY-Blocks. Innerhalb eines TRY-Blocks machen sie eine Transaktion zu einer Transaktion, für die kein Commit ausgeführt werden kann.

Der Code in einem CATCH-Block sollte den Status einer Transaktion mithilfe der XACT_STATE-Funktion testen. XACT_STATE gibt den Wert -1 zurück, falls die Sitzung eine Transaktion aufweist, für die kein Commit ausgeführt werden kann. Der CATCH-Block darf keine Aktionen ausführen, die Schreibvorgänge im Protokoll generieren würden, wenn XACT_STATE den Wert -1 zurückgibt. Im folgenden Codebeispiel wird ein Fehler aus einer DDL-Anweisung generiert und mithilfe von XACT_STATE der Status einer Transaktion getestet, um die am besten geeignete Aktion auszuführen.

USE AdventureWorks2008R2;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_books', N'U') IS NOT NULL
    DROP TABLE my_books;
GO

-- Create table my_books.
CREATE TABLE my_books
    (
    Isbn        int PRIMARY KEY,
    Title       NVARCHAR(100)
    );
GO

BEGIN TRY
    BEGIN TRANSACTION;
        -- This statement will generate an error because the 
        -- column author does not exist in the table.
        ALTER TABLE my_books
            DROP COLUMN author;
    -- If the DDL statement succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;

    -- Test XACT_STATE for 1 or -1.
    -- XACT_STATE = 0 means there is no transaction and
    -- a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT
            N'The transaction is in an uncommittable state. ' +
            'Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT
            N'The transaction is committable. ' +
            'Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

Behandeln von Deadlocks

TRY…CATCH kann für das Behandeln von Deadlocks verwendet werden. Der Deadlockopferfehler 1205 kann vom CATCH-Block erfasst werden, und es kann ein Rollback für die Transaktion ausgeführt werden, bis die Sperre für die Threads aufgehoben wurde. Weitere Informationen zu Deadlocks finden Sie unter Deadlocks.

Im folgenden Beispiel wird gezeigt, wie TRY…CATCH zum Behandeln von Deadlocks verwendet werden kann. Dieser erste Abschnitt erstellt eine Tabelle, mit deren Hilfe ein Deadlockstatus und eine gespeicherte Prozedur veranschaulicht werden, die zum Ausgeben von Fehlerinformationen verwendet werden.

USE AdventureWorks2008R2;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_sales',N'U') IS NOT NULL
    DROP TABLE my_sales;
GO

-- Create and populate the table for deadlock simulation.
CREATE TABLE my_sales 
    (
    Itemid       INT PRIMARY KEY,
    Sales        INT not null
    );
GO

INSERT my_sales (itemid, sales) VALUES (1, 1);
INSERT my_sales (itemid, sales) VALUES (2, 1);
GO
  
-- Verify that the stored procedure for error printing
-- does not exist.
IF OBJECT_ID (N'usp_MyErrorLog',N'P') IS NOT NULL
    DROP PROCEDURE usp_MyErrorLog;
GO

-- Create a stored procedure for printing error information.
CREATE PROCEDURE usp_MyErrorLog
AS
    PRINT 
        'Error ' + CONVERT(VARCHAR(50), ERROR_NUMBER()) +
        ', Severity ' + CONVERT(VARCHAR(5), ERROR_SEVERITY()) +
        ', State ' + CONVERT(VARCHAR(5), ERROR_STATE()) + 
        ', Line ' + CONVERT(VARCHAR(5), ERROR_LINE());
    PRINT 
        ERROR_MESSAGE();
GO

Die folgenden Codeskripts für Sitzung 1 und Sitzung 2 werden gleichzeitig in zwei separaten SQL Server Management Studio-Verbindungen ausgeführt. Beide Sitzungen versuchen, dieselben Zeilen in der Tabelle zu aktualisieren. Eine der Sitzungen führt den Aktualisierungsvorgang beim ersten Versuch erfolgreich durch, und die andere Sitzung wird als Deadlockopfer ausgewählt. Der Deadlockopferfehler verursacht, dass die Ausführung in den CATCH-Block springt, und die Transaktion tritt in den Status ein, in dem kein Commit für sie ausgeführt werden kann. Innerhalb des CATCH-Blockes kann das Deadlockopfer ein Rollback mit der Transaktion durchführen und erneut versuchen, die Tabelle zu aktualisieren, bis die Aktualisierung erfolgreich ist oder die maximale Anzahl der Wiederholungsversuche erreicht ist, je nachdem, welche dieser Situationen zuerst eintritt.

Sitzung 1

Sitzung 2

USE AdventureWorks2008R2;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
-- Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        WAITFOR DELAY '00:00:13';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO
USE AdventureWorks2008R2;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
--Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
       BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        WAITFOR DELAY '00:00:07';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO

TRY…CATCH mit RAISERROR

RAISERROR kann sowohl im TRY- als auch im CATCH-Block eines TRY…CATCH-Konstrukts verwendet werden, um das Verhalten bei der Fehlerbehandlung zu beeinflussen.

Wenn RAISERROR mit einem Schweregrad von 11 bis 19 in einem TRY-Block ausgeführt wird, wird die Steuerung an den zugeordneten CATCH-Block übertragen. Wenn RAISERROR mit einem Schweregrad von 11 bis 19 in einem CATCH-Block ausgeführt wird, wird ein Fehler an die aufrufende Anwendung oder den aufrufenden Batch zurückgegeben. Auf diese Weise kann RAISERROR verwendet werden, um Informationen zu dem Fehler, der die Ausführung des CATCH-Blockes verursacht hat, an den Aufrufer zurückzugeben. Fehlerinformationen, die von TRY…CATCH-Fehlerfunktionen zur Verfügung gestellt werden, können in der RAISERROR-Meldung erfasst werden. Enthalten ist auch die ursprüngliche Fehlernummer, obwohl die Fehlernummer für RAISERROR >= 50000 sein muss.

RAISERROR mit einem Schweregrad von 10 oder niedriger gibt eine Informationsmeldung an den aufrufenden Batch oder die Anwendung zurück, ohne einen CATCH-Block aufzurufen.

RAISERROR mit einem Schweregrad von 20 oder höher beendet die Datenbankverbindung, ohne den CATCH-Block aufzurufen.

Im folgenden Codebeispiel wird veranschaulicht, wie RAISERROR innerhalb eines CATCH-Blocks verwendet werden kann, um die ursprünglichen Fehlerinformationen an die aufrufende Anwendung oder den aufrufenden Batch zurückzugeben. Die gespeicherte Prozedur usp_GenerateError führt eine DELETE-Anweisung innerhalb eines TRY-Blocks aus, die einen Fehler aufgrund einer Einschränkungsverletzung generiert. Der Fehler verursacht, dass die Ausführung an den zugeordneten CATCH-Block in usp_GenerateError übertragen wird, in dem die gespeicherte Prozedur usp_RethrowError ausgeführt wird, um mithilfe von RAISERROR die Fehlerinformationen zu der Einschränkungsverletzung auszugeben. Dieser von RAISERROR generierte Fehler wird an den aufrufenden Batch zurückgegeben, in dem usp_GenerateError ausgeführt wurde, und verursacht, dass die Ausführung an den zugeordneten CATCH-Block in dem aufrufenden Batch übertragen wird.

HinweisHinweis

RAISERROR kann nur Fehler mit dem Status 1 bis 127 generieren. Da Database Engine (Datenbankmodul) möglicherweise Fehler mit dem Status 0 ausgibt, sollten Sie den von ERROR_STATE zurückgegebenen Fehler überprüfen, bevor Sie ihn als Wert an den Statusparameter von RAISERROR übergeben.

USE AdventureWorks2008R2;
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_RethrowError',N'P') IS NOT NULL
    DROP PROCEDURE usp_RethrowError;
GO

-- Create the stored procedure to generate an error using 
-- RAISERROR. The original error information is used to
-- construct the msg_str for RAISERROR.
CREATE PROCEDURE usp_RethrowError AS
    -- Return if there is no error information to retrieve.
    IF ERROR_NUMBER() IS NULL
        RETURN;

    DECLARE 
        @ErrorMessage    NVARCHAR(4000),
        @ErrorNumber     INT,
        @ErrorSeverity   INT,
        @ErrorState      INT,
        @ErrorLine       INT,
        @ErrorProcedure  NVARCHAR(200);

    -- Assign variables to error-handling functions that 
    -- capture information for RAISERROR.
    SELECT 
        @ErrorNumber = ERROR_NUMBER(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE(),
        @ErrorLine = ERROR_LINE(),
        @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

    -- Build the message string that will contain original
    -- error information.
    SELECT @ErrorMessage = 
        N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 
            'Message: '+ ERROR_MESSAGE();

    -- Raise an error: msg_str parameter of RAISERROR will contain
    -- the original error information.
    RAISERROR 
        (
        @ErrorMessage, 
        @ErrorSeverity, 
        1,               
        @ErrorNumber,    -- parameter: original error number.
        @ErrorSeverity,  -- parameter: original error severity.
        @ErrorState,     -- parameter: original error state.
        @ErrorProcedure, -- parameter: original error procedure name.
        @ErrorLine       -- parameter: original error line number.
        );
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_GenerateError',N'P') IS NOT NULL
    DROP PROCEDURE usp_GenerateError;
GO

-- Create a stored procedure that generates a constraint violation
-- error. The error is caught by the CATCH block where it is 
-- raised again by executing usp_RethrowError.
CREATE PROCEDURE usp_GenerateError 
AS 
    BEGIN TRY
        -- A FOREIGN KEY constraint exists on the table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;
    END TRY
    BEGIN CATCH
        -- Call the procedure to raise the original error.
        EXEC usp_RethrowError;
    END CATCH;
GO

-- In the following batch, an error occurs inside 
-- usp_GenerateError that invokes the CATCH block in
-- usp_GenerateError. RAISERROR inside this CATCH block
-- generates an error that invokes the outer CATCH
-- block in the calling batch.
BEGIN TRY  -- outer TRY
    -- Call the procedure to generate an error.
    EXECUTE usp_GenerateError;
END TRY
BEGIN CATCH  -- Outer CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;
END CATCH;
GO

Ändern des Ausführungsflusses

Um den Ausführungsfluss zu ändern, kann GOTO innerhalb eines TRY- oder eines CATCH-Blockes verwendet werden. GOTO kann auch verwendet werden, um einen TRY-Block oder einen CATCH-Block zu beenden. GOTO kann jedoch nicht zum Eingeben eines TRY-Blocks oder eines CATCH-Blocks verwendet werden.

Fehlerbehandlungslösung in der Adventure Works 2008 R2-Beispieldatenbank

Die AdventureWorks2008R2-Beispieldatenbank enthält eine Fehlerbehandlungslösung, die darauf ausgerichtet ist, Informationen zu Fehlern zu protokollieren, die vom CATCH-Block eines TRY...CATCH-Konstrukts erfasst werden und später abgefragt oder analysiert werden können.

dbo.ErrorLog (Tabelle)

Die ErrorLog-Tabelle zeichnet folgende Informationen auf: Fehlerschweregrad, Fehlerzustand, Name der gespeicherten Prozedur oder des Triggers, auf dem der Fehler aufgetreten ist, Nummer der Zeile, in der der Fehler aufgetreten ist und vollständiger Text der Fehlermeldung. Sie zeichnet zudem Datum und Uhrzeit auf, zu der der Fehler aufgetreten ist, sowie den Namen des Benutzers, der die Routine ausgeführt hat, durch die der Fehler generiert wurde. Diese Tabelle wird aufgefüllt, wenn die gespeicherte uspLogError-Prozedur im Bereich des CATCH-Blocks eines TRY…CATCH-Konstrukts ausgeführt wird.

dbo.uspLogError

Die gespeicherte uspLogError-Prozedur protokolliert Fehlerinformationen zu dem Fehler, aufgrund dessen die Ausführung an den CATCH-Block eines TRY...CATCH-Konstrukts übertragen wurde, in der ErrorLog-Tabelle. Folgende Bedingungen müssen erfüllt sein, damit uspLogError Fehlerinformationen in die ErrorLog-Tabelle einfügen kann:

  • uspLogError wird innerhalb des Bereichs eines CATCH-Blockes ausgeführt.

  • Falls sich die aktuelle Transaktion in dem Status befindet, in dem kein Commit für sie ausgeführt werden kann, wird für die Transaktion ein Rollback ausgeführt, bevor uspLogError ausgeführt wird.

Der @ErrorLogID-Ausgabeparameter von uspLogError gibt den ErrorLogID-Wert der Zeile zurück, die durch uspLogError in die ErrorLog-Tabelle eingefügt wurde. Der Standardwert von @ErrorLogID ist 0. Im folgenden Beispiel wird der Code für uspLogError gezeigt.

CREATE PROCEDURE [dbo].[uspLogError] 
    @ErrorLogID [int] = 0 OUTPUT  -- Contains the ErrorLogID of the row inserted
                                  -- by uspLogError in the ErrorLog table.

AS
BEGIN
    SET NOCOUNT ON;

    -- Output parameter value of 0 indicates that error 
    -- information was not logged.
    SET @ErrorLogID = 0;

    BEGIN TRY
        -- Return if there is no error information to log.
        IF ERROR_NUMBER() IS NULL
            RETURN;

        -- Return if inside an uncommittable transaction.
        -- Data insertion/modification is not allowed when 
        -- a transaction is in an uncommittable state.
        IF XACT_STATE() = -1
        BEGIN
            PRINT 'Cannot log error since the current transaction is in an uncommittable state. ' 
                + 'Rollback the transaction before executing uspLogError in order to successfully log error information.';
            RETURN;
        END;

        INSERT [dbo].[ErrorLog] 
            (
            [UserName], 
            [ErrorNumber], 
            [ErrorSeverity], 
            [ErrorState], 
            [ErrorProcedure], 
            [ErrorLine], 
            [ErrorMessage]
            ) 
        VALUES 
            (
            CONVERT(sysname, CURRENT_USER), 
            ERROR_NUMBER(),
            ERROR_SEVERITY(),
            ERROR_STATE(),
            ERROR_PROCEDURE(),
            ERROR_LINE(),
            ERROR_MESSAGE()
            );

        -- Pass back the ErrorLogID of the row inserted
        SELECT @ErrorLogID = @@IDENTITY;
    END TRY
    BEGIN CATCH
        PRINT 'An error occurred in stored procedure uspLogError: ';
        EXECUTE [dbo].[uspPrintError];
        RETURN -1;
    END CATCH
END; 

dbo.uspPrintError

Die gespeicherte uspPrintError-Prozedur gibt Informationen zu dem Fehler aus, der verursacht hat, dass die Ausführung an den CATCH-Block eines TRY…CATCH-Konstrukts übertragen wurde. uspPrintError sollte im Bereich eines CATCH-Blocks ausgeführt werden, da andernfalls die Rückgabe ohne Fehlerinformationen erfolgt. Im folgenden Beispiel wird der Code für uspPrintError gezeigt.

CREATE PROCEDURE [dbo].[uspPrintError] 
AS
BEGIN
    SET NOCOUNT ON;

    -- Print error information. 
    PRINT 'Error ' + CONVERT(varchar(50), ERROR_NUMBER()) +
          ', Severity ' + CONVERT(varchar(5), ERROR_SEVERITY()) +
          ', State ' + CONVERT(varchar(5), ERROR_STATE()) + 
          ', Procedure ' + ISNULL(ERROR_PROCEDURE(), '-') + 
          ', Line ' + CONVERT(varchar(5), ERROR_LINE());
    PRINT ERROR_MESSAGE();
END;

Beispiel für die Fehlerbehandlung

Im folgenden Beispiel wird die Fehlerbehandlungslösung von AdventureWorks2008R2 veranschaulicht. Der Code innerhalb des TRY-Blocks versucht, den Datensatz mit ProductID 980 in der Production.Product-Tabelle zu löschen. Durch eine FOREIGN KEY-Einschränkung für die Tabelle wird verhindert, dass die DELETE-Anweisung erfolgreich ausgeführt wird, und es wird ein Fehler aufgrund einer Einschränkungsverletzung generiert. Dieser Fehler verursacht, dass die Ausführung an den CATCH-Block übertragen wird. Innerhalb des CATCH-Blocks finden die folgenden Aktionen statt:

  • uspPrintError gibt die Fehlerinformationen aus.

  • Nach dem das Rollback der Transaktion durchgeführt wurde, gibt uspLogError die Informationen in die ErrorLog-Tabelle ein und gibt die ErrorLogID der eingefügten Zeile in den @ErrorLogID OUTPUT-Parameter ein.

USE AdventureWorks2008R2;
GO

-- Variable to store ErrorLogID value of the row
-- inserted in the ErrorLog table by uspLogError 
DECLARE @ErrorLogID INT;

BEGIN TRY
    BEGIN TRANSACTION;

    -- A FOREIGN KEY constraint exists on this table. This 
    -- statement will generate a constraint violation error.
    DELETE FROM Production.Product
        WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Call procedure to print error information.
    EXECUTE dbo.uspPrintError;

    -- Roll back any active or uncommittable transactions before
    -- inserting information in the ErrorLog.
    IF XACT_STATE() <> 0
    BEGIN
        ROLLBACK TRANSACTION;
    END

    EXECUTE dbo.uspLogError @ErrorLogID = @ErrorLogID OUTPUT;
END CATCH; 

-- Retrieve logged error information.
SELECT * FROM dbo.ErrorLog WHERE ErrorLogID = @ErrorLogID;
GO

Beispiel für die Fehlerbehandlung bei Verschachtelung

Im folgenden Beispiel wird das Verwenden geschachtelter TRY…CATCH-Konstrukte dargestellt.

BEGIN TRY
    BEGIN TRY
        SELECT CAST('invalid_date' AS datetime)
    END TRY
    BEGIN CATCH 
        PRINT 'Inner TRY error number: ' +    
            CONVERT(varchar,ERROR_NUMBER()) + ' on line: ' +
            CONVERT(varchar, ERROR_LINE())
    END CATCH
    SELECT CAST('invalid_int' AS int)
END TRY
BEGIN CATCH
    PRINT 'Outer TRY error mumber: ' + CONVERT(varchar,ERROR_NUMBER())+ 
            ' on line: ' + CONVERT(varchar, ERROR_LINE())
END CATCH

Dies ist das Resultset.

Inner TRY error number: 241 on line: 3

Outer TRY error number: 245 on line: 9