預存程序中正確處理交易是資料一致性的關鍵。預設下 SQL Server 遇到執行時錯誤 (如違反 NOT NULL) 僅回滾**該陳述式**,繼續執行後續陳述式,導致交易部分成功。本文對比三種交易控制模式並給出標準範本。
CREATE TABLE demo (id INT NOT NULL);
GO
BEGIN TRAN
INSERT INTO demo VALUES (NULL); -- 違反 NOT NULL,報錯
INSERT INTO demo VALUES (2); -- 仍會執行成功!
COMMIT TRAN;
GO
SELECT * FROM demo; -- 發現值 2 被寫入,交易未整體回滾
CREATE PROC usp_Demo_XactAbort AS
BEGIN
SET XACT_ABORT ON; -- 遇任何執行時錯誤:終止批次、回滾整個交易
BEGIN TRAN
UPDATE ...;
INSERT ...;
DELETE ...;
COMMIT TRAN;
END
GO
CREATE PROC usp_Demo_ErrorCheck AS
BEGIN
BEGIN TRAN
UPDATE ...;
IF @@ERROR <> 0 BEGIN ROLLBACK TRAN; RETURN -1; END
DELETE ...;
IF @@ERROR <> 0 BEGIN ROLLBACK TRAN; RETURN -1; END
COMMIT TRAN;
RETURN 0;
END
GO
CREATE PROC usp_Demo_TryCatch AS
BEGIN
SET XACT_ABORT ON; -- 建議搭配:確保嚴重錯誤也能進 CATCH
BEGIN TRY
BEGIN TRAN
UPDATE ...;
DELETE ...;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRAN;
-- 記錄錯誤資訊
INSERT INTO ErrorLog (ProcName, ErrorNumber, ErrorMessage, ErrorTime)
VALUES (OBJECT_NAME(@@PROCID), ERROR_NUMBER(), ERROR_MESSAGE(), GETDATE());
-- 可選:重新拋出給上層
THROW;
RETURN -1;
END CATCH
RETURN 0;
END
GO
| 函數 | 用途 |
|---|---|
| ERROR_NUMBER() | 錯誤號 |
| ERROR_SEVERITY() | 嚴重度 |
| ERROR_STATE() | 狀態碼 |
| ERROR_PROCEDURE() | 發生錯誤的預存程序/觸發器名稱 |
| ERROR_LINE() | 錯誤行號 |
| ERROR_MESSAGE() | 完整錯誤訊息 |
| 場景 | 推薦模式 | 理由 |
|---|---|---|
| 一般業務預存程序、追求簡潔 | SET XACT_ABORT ON | 零侵入、全覆蓋、維護成本最低 |
| 需區分錯誤類型處理 (如死鎖重試、特定約束記錄) | TRY/CATCH | 結構化、豐富錯誤資訊、支援巢狀交易 |
| 舊系統相容、極簡環境 | @@ERROR 檢查 | 無結構化支援時的備選 |
| 嚴格生產環境標準 | TRY/CATCH + SET XACT_ABORT ON | 雙重保障:TRY/CATCH 處理邏輯錯誤,XACT_ABORT 兜底嚴重錯誤 |
CREATE PROC usp_BusinessOperation @Param1 INT, @Param2 NVARCHAR(50) AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- 兜底:任何執行時錯誤直接回滾
BEGIN TRY
BEGIN TRAN
-- 核心業務邏輯
UPDATE TableA SET Col1 = @Param2 WHERE Id = @Param1;
INSERT INTO TableB (ColA, ColB) VALUES (@Param1, @Param2);
DELETE FROM TableC WHERE RefId = @Param1;
COMMIT TRAN;
RETURN 0;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRAN;
-- 記錄詳細錯誤
DECLARE @ErrMsg NVARCHAR(4000) = ERROR_MESSAGE();
INSERT INTO dbo.ErrorLog (ProcName, ErrorNumber, ErrorSeverity, ErrorState,
ErrorProcedure, ErrorLine, ErrorMessage, LogTime)
VALUES (OBJECT_NAME(@@PROCID), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(),
ERROR_PROCEDURE(), ERROR_LINE(), @ErrMsg, SYSDATETIME());
-- 回傳給呼叫端 (可選)
RAISERROR('Business operation failed: %s', 16, 1, @ErrMsg);
RETURN -1;
END CATCH
END
GO
採用「TRY/CATCH + SET XACT_ABORT ON + 統一錯誤日誌」作為團隊預存程序交易標準,可兼顧正確性、可觀測性與維護效率。