在 Transact-SQL 中使用 TRY...CATCH

您可以使用 TRY...CATCH 建構來處理 Transact-SQL 程式碼中的錯誤,這個建構類似於 Microsoft Visual C++ 和 Microsoft Visual C# 語言的例外狀況處理功能。TRY...CATCH 建構由兩部分所組成:TRY 區塊與 CATCH 區塊。在 TRY 區塊內的 Transact-SQL 陳述式中偵測到錯誤狀況時,控制權會傳遞給可處理此錯誤的 CATCH 區塊。

CATCH 區塊處理例外狀況之後,控制權會接著傳遞給 END CATCH 陳述式後面的第一個 Transact-SQL 陳述式。如果 END CATCH 陳述式是預存程序或觸發程序中的最後一個陳述式,則控制權會傳回給叫用此預存程序或觸發程序的程式碼。在 TRY 區塊中,產生錯誤之陳述式後面的 Transact-SQL 陳述式將不會執行。

如果 TRY 區塊內沒有錯誤,控制權會傳遞給緊接在相關聯 END CATCH 陳述式後面的陳述式。如果 END CATCH 陳述式是預存程序或觸發程序中的最後一個陳述式,則控制權會傳遞給叫用此預存程序或觸發程序的陳述式。

TRY 區塊是以 BEGIN TRY 陳述式開始,而以 END TRY 陳述式結束。在 BEGIN TRY 與 END TRY 陳述式之間,可以指定一或多個 Transact-SQL 陳述式。

TRY 區塊後面必須緊接著 CATCH 區塊。CATCH 區塊是以 BEGIN CATCH 陳述式開始,而以 END CATCH 陳述式結束。在 Transact-SQL 中,每一個 TRY 區塊只能與一個 CATCH 區塊相關聯。

使用 TRY...CATCH

使用 TRY...CATCH 建構時,請考慮下列指導方針及建議:

  • 每一個 TRY...CATCH 建構都必須位於單一批次、預存程序或觸發程序中。例如,您無法將 TRY 區塊放置於某個批次,然後將相關聯的 CATCH 區塊放置於另一個批次。下列指令碼會產生錯誤:

    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
    
  • TRY 區塊後面必須緊接著 CATCH 區塊。

  • TRY…CATCH 建構可以有巢狀結構。這就表示 TRY…CATCH 建構可以放置於其他 TRY 和 CATCH 區塊內。當巢狀 TRY 區塊內發生錯誤時,程式控制權會傳送給與巢狀 TRY 區塊相關聯的 CATCH 區塊。

  • 若要處理發生在給定的 CATCH 區塊內的錯誤,請在指定的 CATCH 區塊內編寫 TRY......CATCH 區塊。

  • TRY...CATCH 區塊不會處理嚴重性 20 以上且導致 Database Engine 關閉連接的錯誤。不過,只要連接未關閉,TRY...CATCH 就會處理嚴重性 20 以上的錯誤。

  • 嚴重性 10 以下的錯誤則視為警告或參考用訊息,而且 TRY...CATCH 區塊不會進行處理。

  • 注意事項會結束批次,即使該批次是在 TRY...CATCH 建構的範圍內。這包括當分散式交易失敗時,Microsoft 分散式交易協調器 (MS DTC) 所傳送的注意事項。MS DTC 負責管理分散式交易。

    [!附註]

    如果分散式交易在 TRY 區塊的範圍內執行,並且發生錯誤,執行動作就會傳送給相關聯的 CATCH 區塊。此時,分散式交易會進入無法認可的狀態。CATCH 區塊內的執行動作可能會被管理分散式交易的 Microsoft 分散式交易協調器中斷。當發生錯誤時,MS DTC 會以非同步方式通知所有參與該分散式交易的伺服器,並結束分散式交易中包含的所有工作。此通知是以注意事項的格式傳送,TRY...CATCH 建構不會處理它,而且批次會結束。當批次執行完成時,Database Engine 會回復任何使用中且無法認可的交易。如果在交易進入無法認可的狀態時沒有傳送任何錯誤訊息,則當批次完成時,就會將錯誤訊息傳送給用戶端應用程式,表示已偵測並回復無法認可的交易。如需分散式交易的詳細資訊,請參閱<分散式交易 (Database Engine)>。

錯誤函數

TRY...CATCH 會使用下列錯誤函數來擷取錯誤資訊:

  • ERROR_NUMBER() 會傳回錯誤號碼。

  • ERROR_MESSAGE() 會傳回錯誤訊息的完整文字。這段文字包括提供給可替代參數的值,例如長度、物件名稱或時間。

  • ERROR_SEVERITY() 傳回錯誤嚴重性。

  • ERROR_STATE() 會傳回錯誤狀態碼。

  • ERROR_LINE() 會傳回常式內造成錯誤的行號。

  • ERROR_PROCEDURE() 會傳回發生錯誤的預存程序或觸發程序的名稱。

您可在 TRY...CATCH 建構之 CATCH 區塊範圍內的任何位置,使用這些函數來擷取錯誤資訊。如果在 CATCH 區塊範圍外呼叫錯誤函數,將傳回 NULL。如果在 CATCH 區塊內執行預存程序,便可在預存程序內參考錯誤函數,而且可以使用這些錯誤函數來擷取錯誤資訊。進行這項動作時,您不需要在每個 CATCH 區塊中重複錯誤處理程式碼。在下列程式碼範例中,TRY 區塊內的 SELECT 陳述式將會產生除以零的錯誤。CATCH 區塊將處理此錯誤,並使用預存程序來傳回錯誤資訊。

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

編譯與陳述式層級重新編譯錯誤

如果錯誤是發生在與 TRY...CATCH 建構相同的執行層級,則 TRY...CATCH 建構不會處理下列兩種錯誤類型:

  • 編譯錯誤,例如會阻礙批次執行的語法錯誤。

  • 在陳述式層級重新編譯期間發生的錯誤,例如在編譯之後,因延遲的名稱解析所發生的物件名稱解析錯誤。

當包含 TRY...CATCH 建構的批次、預存程序或觸發程序產生上述其中一種錯誤時,TRY...CATCH 建構不會處理這些錯誤。這些錯誤將傳回給呼叫錯誤產生常式的應用程式或批次。例如,下列程式碼範例顯示會導致語法錯誤的 SELECT 陳述式。如果這個程式碼是在 SQL Server Management Studio 查詢編輯器中執行,就會因為批次無法編譯,所以無法開始執行。錯誤將傳回至查詢編輯器,而 TRY...CATCH 無法捕捉此錯誤。

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

與上述範例中的語法錯誤不同的是,在陳述式層級重新編譯期間發生的錯誤並不會阻礙批次進行編譯,但是只要陳述式重新編譯失敗,就會結束批次。例如,如果批次具有兩個陳述式,而第二個陳述式參考了不存在的資料表,則延遲的名稱解析功能會讓批次編譯成功,並在未將遺漏的資料表繫結到查詢計畫的狀況下便開始執行,而這種狀況需等到該陳述式重新編譯後,才能解決。當批次進行到參考了遺漏資料表的陳述式時,便會停止執行,並傳回錯誤。在發生錯誤的相同執行層級中,TRY...CATCH 建構並不會處理此類型的錯誤。下列範例示範此行為。

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

您可以使用 TRY...CATCH 來處理在編譯或陳述式層級重新編譯期間所發生的錯誤,方法是在 TRY 區塊的個別批次中執行產生錯誤的程式碼。例如,您可以將程式碼放置於預存程序中,或使用 sp_executesql 執行動態 Transact-SQL 陳述式來達成此目標。這樣就可讓 TRY...CATCH 在比錯誤發生時更高的執行層級中捕捉錯誤。例如,下列程式碼範例將顯示一個會產生物件名稱解析錯誤的預存程序。包含 TRY...CATCH 建構的批次是在比預存程序更高的層級執行,因此可以捕捉到在較低層級發生的錯誤。

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

以下為結果集:

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

如需詳細資訊,請參閱<延遲名稱解析及編譯>,以及<執行計畫快取與重複使用>中的<重新編譯執行計畫>一節。

無法認可的交易

在 TRY...CATCH 建構內,交易會進入保持開啟但無法認可的狀態。交易無法執行任何會寫入交易記錄的動作,例如修改資料或嘗試回復到儲存點。不過,在此狀態下,仍會維護交易所取得的鎖定,也會保持連接開啟。要等到發出 ROLLBACK 陳述式,或批次結束並且 Database Engine 自動回復交易時,才會反轉交易的結果。如果在交易進入無法認可的狀態時沒有傳送任何錯誤訊息,則當批次完成時,就會將錯誤訊息傳送給用戶端應用程式,表示已偵測並回復無法認可的交易。

若發生了正常情況下會結束交易的錯誤,TRY 區塊內的該交易將進入無法認可的狀態。例如,資料定義語言 (DDL) 陳述式 (如 CREATE TABLE) 中的大部分錯誤,或當 SET XACT_ABORT 設為 ON 時發生的大部分錯誤,在 TRY 區塊外都會結束交易,但在 TRY 區塊內則會讓交易成為無法認可。

CATCH 區塊中的程式碼應該使用 XACT_STATE 函數來測試交易的狀態。如果工作階段有無法認可的交易,XACT_STATE 將傳回 -1。如果 XACT_STATE 傳回 -1,CATCH 區塊絕不能執行任何會寫入記錄的動作。下列程式碼範例將產生 DDL 陳述式的錯誤,並使用 XACT_STATE 來測試交易的狀態,以便採取最適當的動作。

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

處理死結

TRY...CATCH 可以用來處理死結。CATCH 區塊可以捕捉到 1205 死結犧牲者錯誤,而且可以回復交易,直到執行緒解除鎖定。如需有關死結的詳細資訊,請參閱<死結>。

下列範例將說明如何使用 TRY...CATCH 來處理死結。以下第一個區段會建立一個用來展示死結狀態的資料表,以及一個用來列印錯誤資訊的預存程序。

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

下列的工作階段 1 及工作階段 2 指令碼,是在兩個個別的 SQL Server Management Studio 連接中同步執行。這兩個工作階段嘗試更新資料表中的同一個資料列。其中一個工作階段在第一次嘗試時便能順利完成更新作業,而另一個工作階段則被選為死結犧牲者。死結犧牲者錯誤會使執行動作跳至 CATCH 區塊,而交易將進入無法認可的狀態。在 CATCH 區塊內,死結犧牲者可以回復交易並再次嘗試更新資料表,直到更新成功或達到重試次數限制,視何者先發生而定。

工作階段 1

工作階段 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

具有 RAISERROR 的 TRY...CATCH

RAISERROR 可以用在 TRY...CATCH 建構的 TRY 或 CATCH 區塊中,以影響處理錯誤的行為。

在 TRY 區塊內執行而且具有嚴重性 11 到 19 的 RAISERROR 會將控制權傳送到相關聯的 CATCH 區塊。在 CATCH 區塊內執行而且具有嚴重性 11 到 19 的 RAISERROR 會將錯誤傳回進行呼叫的應用程式或批次。如此一來,RAISERROR 可以將導致 CATCH 區塊執行的錯誤詳細資訊傳回給呼叫者。TRY...CATCH 錯誤函數所提供的錯誤資訊可以在 RAISERROR 訊息中擷取,其中包括原始錯誤號碼;不過,RAISERROR 的錯誤號碼必須 >= 50000。

嚴重性 10 以下的 RAISERROR 會傳回參考用訊息給進行呼叫的批次或應用程式,而不叫用 CATCH 區塊。

嚴重性 20 以上的 RAISERROR 會關閉資料庫連接,而不叫用 CATCH 區塊。

下列程式碼範例將說明如何在 CATCH 區塊內使用 RAISERROR,將原始錯誤資訊傳回進行呼叫的應用程式或批次。預存程序 usp_GenerateError 會在 TRY 區塊內執行 DELETE 陳述式,以便產生條件約束違規錯誤。此錯誤會導致執行動作傳送給執行預存程序 usp_RethrowError 之 usp_GenerateError 內的相關聯 CATCH 區塊,以便使用 RAISERROR 來引發條件約束違規錯誤資訊。RAISERROR 所產生的這項錯誤資訊會傳回執行 usp_GenerateError 的呼叫批次,並導致執行動作傳送給呼叫批次內的相關聯 CATCH 區塊。

[!附註]

RAISERROR 只能產生狀態為 1 到 127 的錯誤。因為 Database Engine 可能會引發狀態 0 的錯誤,所以我們建議您在傳送 ERROR_STATE 的值給 RAISERROR 的狀態參數之前,先檢查它傳回的錯誤狀態。

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

變更執行流程

若要變更執行流程,可以在 TRY 區塊或 CATCH 區塊內使用 GOTO。GOTO 也可以用來結束 TRY 區塊或 CATCH 區塊;然而,GOTO 無法用來進入 TRY 區塊或 CATCH 區塊。

AdventureWorks2008R2 範例資料庫中的錯誤處理方案

AdventureWorks2008R2 範例資料庫包含一個錯誤處理方案,其設計目的是用來記錄 TRY...CATCH 建構的 CATCH 區塊所捕捉到的錯誤相關資訊,而這些資訊之後可以進行查詢或分析。

dbo.ErrorLog 資料表

ErrorLog 資料表所記錄的資訊包括錯誤號碼、錯誤嚴重性、錯誤狀態、發生錯誤之預存程序或觸發程序的名稱、發生錯誤的行號以及錯誤訊息的完整文字。同時也記錄發生錯誤的日期及時間,以及執行了產生錯誤之常式的使用者名稱。在 TRY...CATCH 建構的 CATCH 區塊範圍內執行預存程序 uspLogError 時,即會填入此資料表。

dbo.uspLogError

預存程序 uspLogError 會將導致執行動作傳送給 TRY...CATCH 建構 CATCH 區塊的錯誤相關資訊,記錄在 ErrorLog 資料表中。若要讓 uspLogError 能夠將錯誤資訊插入 ErrorLog 資料表,必須存在下列條件:

  • uspLogError 是在 CATCH 區塊的範圍內執行。

  • 如果目前交易處於無法認可的狀態,就會先回復交易,然後再執行 uspLogError。

uspLogError 的輸出參數 @ErrorLogID,會針對由 uspLogError 插入到 ErrorLog 資料表中的資料列,傳回此資料列的 ErrorLogID。@ErrorLogID 的預設值是 0。下列範例將顯示 uspLogError 的程式碼。

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

預存程序 uspPrintError 會針對將執行動作傳送給 TRY…CATCH 建構之 CATCH 區塊的錯誤,列印其相關資訊。uspPrintError 應該在 CATCH 區塊的範圍內執行,否則此程序會傳回但不會列印任何錯誤資訊。下列範例將顯示 uspPrintError 的程式碼。

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;

錯誤處理範例

下列範例將說明 AdventureWorks2008R2 錯誤處理方案。TRY 區塊內的程式碼會嘗試刪除 Production.Product 資料表中具有 ProductID 980 的記錄。資料表上的 FOREIGN KEY 條件約束會防止 DELETE 作業順利完成,而且會產生條件約束違規錯誤。此錯誤會導致執行動作傳送給 CATCH 區塊。在 CATCH 區塊內,將發生下列動作:

  • uspPrintError 列印錯誤資訊。

  • 在回復交易之後,uspLogError 會將錯誤資訊輸入 ErrorLog 資料表,並將插入資料列的 ErrorLogID 傳回給 @ErrorLogID OUTPUT 參數。

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

巢狀錯誤處理範例

下列範例示範如何使用巢狀 TRY…CATCH 建構。

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

以下為結果集:

Inner TRY error number: 241 on line: 3

Outer TRY error number: 245 on line: 9