用 TRY...CATCH 處理錯誤
小提示
有關更多詳細信息,請參閱 文本和圖像 選項卡!
生產資料庫應用程式必須優雅地處理突發狀況。 除以零、限制違規、連線逾時以及無效資料都可能導致錯誤。 未處理的錯誤會導致不清楚的錯誤訊息、交易不完整或應用程式當機。 正確的錯誤處理能確保你的 T-SQL 程式碼可預測地失敗,並提供有意義的回饋。
資料庫操作同時與多個使用者、外部系統及不可預測的資料輸入互動。 與應用程式代碼可能透過重試從失敗操作中恢復不同,資料庫錯誤可能使資料處於不一致狀態,有些資料列已插入而其他未插入,或鎖被無限期保留住。 錯誤處理將這些混亂的失效模式轉化為受控且可預測的回應。
良好的錯誤處理能在多方面改善你的程式碼:
資料完整性保護:當操作中途失敗時,適當的錯誤處理能確保所有變更要麼同時提交,要麼都不會生效。 若無此,多步驟流程可能導致資料庫出現孤兒紀錄、總數不符或關係破裂。
除錯效率:擷取錯誤細節如行號、程序名稱及特定錯誤訊息,能加快故障排除速度。 你不必在日誌中尋找模糊的失敗,而是能精確找出錯誤發生的地點與原因。
使用者體驗:應用程式可以顯示有意義的訊息,例如「產品 ID 不存在」,而非隱晦的資料庫錯誤。 這有助於使用者了解問題所在以及如何修復。
操作可視性:將錯誤記錄到專用資料表,建立稽核追蹤,有助於識別模式,例如反覆出現的限制違規(顯示錯誤)或逾時錯誤(暗示效能問題)。
優雅降級:當某項操作失敗時,錯誤處理會讓其餘程式碼繼續或採取替代行動,而非整個批次或儲存程序當機。
實作 T-SQL 錯誤處理
T-SQL 透過區塊提供結構化錯誤處理 TRY...CATCH ,類似於其他程式語言中的例外處理。 當區塊發生 TRY 錯誤時,執行會轉移到 CATCH 該區塊,讓你能妥善處理該錯誤:
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;
若不處理錯誤,相同程式碼將以錯誤訊息結束:
SELECT 1/0; -- Msg 8134: Divide by zero error encountered
備註
TRY...CATCH 無法捕捉所有錯誤。 編譯錯誤(語法錯誤、缺少物件)以及嚴重度 20 或以上導致連線關閉的錯誤,無法在同一個會話中被捕捉。
擷取錯誤資訊
在區 CATCH 塊內,SQL Server 提供以下功能以擷取發生錯誤的詳細資訊:
| 功能 | Description |
|---|---|
ERROR_NUMBER() |
回傳錯誤編號 |
ERROR_MESSAGE() |
回傳完整的錯誤訊息文字 |
ERROR_SEVERITY() |
回傳錯誤嚴重度(0-25) |
ERROR_STATE() |
回傳錯誤狀態號碼 |
ERROR_LINE() |
回傳錯誤發生的行號 |
ERROR_PROCEDURE() |
回傳儲存程序或觸發器的名稱 |
以下範例示範如何捕捉錯誤細節並將其記錄到表格中以便後續分析:
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;
小提示
在重新提出錯誤前,務必先記錄錯誤。 一旦你使用 THROW 或 RAISERROR,如果再次呼叫,錯誤函式會返回 NULL。
使用 TRY...CATCH 處理交易
當交易中發生錯誤時,您必須明確復原尚未提交的工作。 這個 @@TRANCOUNT 函式會告訴你交易是否仍在運作:
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;
這項 @@TRANCOUNT 檢查很重要,因為:
- 在
BEGIN TRANSACTION之前可能會發生錯誤 - 在到達
CATCH前,有些錯誤會自動復原交易。 - 嘗試在沒有活躍的交易情況下回溯會引發另一個錯誤
這很重要
在@@TRANCOUNT區塊中呼叫ROLLBACK TRANSACTION前,務必先檢查CATCH。 這可避免錯誤「ROLLBACK TRANSACTION 要求沒有對應的 BEGIN TRANSACTION」。
使用 THROW 引發自訂錯誤
該 THROW 語句會提出一個例外,並附上自訂錯誤編號和訊息。 用它來標示應用特定的錯誤條件:
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;
使用者定義錯誤的自訂錯誤號碼必須是 50000 或以上。 狀態參數(範例中的 1)是使用者定義的值,介於 1 到 255 之間,有助於辨識錯誤發生地點。
使用RAISERROR來格式化訊息
RAISERROR 比 THROW 提供更多的格式化選項,包括 printf 風格的參數替換。 在錯誤訊息中包含執行時值,能讓除錯更簡單,因為你可以精確看到哪些資料導致了失敗,而不必翻查日誌或重現問題:
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;
備註
THROW 是建議用於新程式碼的方法,因為它比較簡單,並且總會提供堆疊追蹤。 當你需要格式化訊息或與現有錯誤處理模式相容時使用 RAISERROR 。
實作巢狀錯誤處理
呼叫其他程序的儲存程序需要協調錯誤處理。 每個層級都應該處理自己的清理,並適當地傳播錯誤:
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;
使用 XACT_ABORT 進行自動復原
您可以設定 XACT_ABORT ON 讓 SQL Server 在任何錯誤發生時自動復原該交易,即使沒有 TRY...CATCH,例如:
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;
結合 XACT_ABORT 與 TRY...CATCH 可以同時享有兩種方法的好處:XACT_ABORT 保證任何錯誤都能立即回滾,而 TRY...CATCH 則允許您記錄錯誤細節,並在錯誤傳播前進行自訂清理。
-- 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;
小提示
對於儲存程序,尤其是涉及多個操作的程序,使用 SET XACT_ABORT ON 是最佳做法。 它確保無論發生哪種特定錯誤,行為都能保持一致。
欲了解更多錯誤處理資訊,請參見 TRY...CATCH(Transact-SQL)和THROW(Transact-SQL)。