首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

SQL Server 異常處理基礎 (一):@@ERROR、RAISERROR、TRY/CATCH 與 sys.messages 完整體系

     編輯:關於SqlServer

前言:資料庫層面的異常處理為何重要?

應用層例外處理 (try/catch/finally) 眾所周知,但資料庫引擎內部的錯誤識別、拋出、捕捉、訊息管理機制常被忽視。SQL Server 提供 @@ERROR、RAISERROR、TRY/CATCH、THROW (2012+) 及系統表 sys.messages 構建完整異常處理體系。本文系統梳理核心概念、常見陷阱與最佳實務。

一、@@ERROR:易變的全域錯誤旗標

DECLARE @Result INT;
SET @Result = 10 / 0;      -- 除零錯誤
PRINT @@ERROR;             -- 輸出 8134 (sys.messages.message_id)
GO

關鍵特性:

  • 僅反映**緊前一條**陳述式的執行結果。
  • **任何後續陳述式 (含 IF、PRINT、SET) 都會重置為 0**。
  • 必須**立即儲存**至變數才可供後續判斷:
UPDATE T_USERS_IN_ROLES SET USER_ID = 'dummy';
IF @@ERROR = 547          -- 547 = FK 違反
    PRINT '使用者不存在'; -- 若中間插入 PRINT @@ERROR,則此處永遠為 0

二、sys.messages:錯誤訊息的中央資料庫

所有系統/自訂錯誤訊息集中存放於 sys.messages:

欄位說明
message_id錯誤號 (系統錯誤 < 50000,自訂 ≥ 50000)
language_id語言代碼 (1033=英文, 1028=繁中),支援多語系
severity嚴重等級 (1-25),決 TRY/CATCH 捕捉行為
is_event_logged是否寫入 Windows Event Log / SQL Error Log
text訊息模板,可含佔位符 %s, %d
-- 驗證 8134 對應訊息
DECLARE @Err INT = 8134;
SELECT * FROM sys.messages WHERE message_id = @Err AND language_id = 1033;

新增自訂訊息 (需 sysadmin)

EXEC sp_addmessage 
    @msgnum = 50001, 
    @severity = 16, 
    @msgtext = N'使用者 %s 已存在,無法重複建立', 
    @lang = 'us_english', 
    @with_log = 'FALSE';
GO
-- 移除:EXEC sp_dropmessage 50001;

三、RAISERROR:主動拋出錯誤的經典指令

RAISERROR ( message_id | msg_str, severity, state [, argument ... ] ) [ WITH option ... ]
  • message_id:sys.messages 登錄號 (推薦,支援多語系、參數化)。
  • msg_str:直接字串 (Number 固定 50000,不推薦)。
  • severity:11-16 可被 TRY/CATCH 捕捉;17-19 需 sysadmin;20-25 斷開連線。
  • state:1-255,區分同一錯誤在不同位置拋出。
  • WITH LOG:強制寫入錯誤日誌 (需 sysadmin)。
  • WITH SETERROR:強制設定 @@ERROR 為指定 message_id (解決字串模式 Number=50000 問題)。

範例:預存程序中拋出業務錯誤

CREATE PROC P_USERS_I @UserName NVARCHAR(256), @Flag INT OUTPUT
AS
BEGIN
    IF EXISTS (SELECT 1 FROM T_USERS WHERE UserName = @UserName)
    BEGIN
        RAISERROR(50001, 16, 1, @UserName); -- 拋出自訂錯誤 50001
        SET @Flag = -1;
        RETURN;
    END
    INSERT INTO T_USERS (UserName) VALUES (@UserName);
    SET @Flag = @@ERROR; -- 0 成功
END

四、TRY/CATCH:結構化錯誤捕捉 (SQL 2005+)

BEGIN TRY
    -- 高風險操作區塊
    INSERT INTO T_USERS (UserName) VALUES ('Duplicate'); -- 假設 PK 違反
END TRY
BEGIN CATCH
    -- 錯誤處理區塊
    SELECT 
        ERROR_NUMBER()   AS ErrNum,    -- 2627
        ERROR_SEVERITY() AS Sev,       -- 14
        ERROR_STATE()    AS St,        -- 1
        ERROR_PROCEDURE() AS Proc,     -- NULL (非模組內) 或程序名
        ERROR_LINE()     AS Line,      -- 行號
        ERROR_MESSAGE()  AS Msg;       -- 完整訊息
    
    -- 可選:重新拋出 (SQL 2012+ 推薦 THROW)
    -- RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE());
    THROW; -- 保留原始堆疊、錯誤號、嚴重度、狀態
END CATCH

TRY/CATCH 關鍵規則

規則說明
捕捉範圍Severity 11-19 的錯誤;Severity 20+ 會中斷連線,不進入 CATCH
嵌套支援CATCH 區塊內可再嵌套 TRY/CATCH
交易狀態進入 CATCH 時交易進入 不可提交狀態 (XACT_STATE() = -1),只能 ROLLBACK;若 XACT_STATE() = 1 可 COMMIT
THROW vs RAISERRORTHROW (2012+):保留原始錯誤資訊、堆疊,語法簡潔;RAISERROR:需手動傳遞 ERROR_*(),可自訂訊息、支援 WITH LOG

五、輸出參數模式:舊版相容的業務錯誤回傳

SQL 2005 前無 TRY/CATCH,常用 OUTPUT 參數回傳執行狀態:

CREATE PROC P_USERS_I 
    @UserID VARCHAR(50), @UserName NVARCHAR(256), @Flag INT OUTPUT
AS
IF EXISTS (SELECT 1 FROM T_USERS WHERE UserName = @UserName)
BEGIN
    SET @Flag = -1; -- 業務錯誤代碼
    RETURN;
END
INSERT INTO T_USERS (UserID, UserName) VALUES (@UserID, @UserName);
SET @Flag = @@ERROR; -- 0 成功,非 0 系統錯誤
缺點:呼叫端必須檢查輸出參數,易被忽略;無法攜帶豐富錯誤資訊;無法與 TRY/CATCH 協作。現代開發**強烈建議改用 RAISERROR/THROW + TRY/CATCH**。

六、ADO.NET 呼叫端處理模式

public bool CreateUser(string userName)
{
    using (var conn = new SqlConnection(connStr))
    using (var cmd = new SqlCommand("P_USERS_I", conn) { CommandType = CommandType.StoredProcedure })
    {
        cmd.Parameters.AddWithValue("@UserName", userName);
        var flagParam = cmd.Parameters.Add("@Flag", SqlDbType.Int);
        flagParam.Direction = ParameterDirection.Output;

        try
        {
            conn.Open();
            cmd.ExecuteNonQuery();
            int flag = (int)flagParam.Value;
            return flag == 0; // 輸出參數模式
        }
        catch (SqlException ex) // RAISERROR/THROW 模式
        {
            if (ex.Number == 50001) return false; // 業務錯誤
            throw; // 其他系統錯誤向上拋出
        }
    }
}

七、總結:異常處理演進路線圖

時代核心機制特徵現狀
SQL 2000 前@@ERROR + OUTPUT 參數手動檢查、易遺漏、無堆疊僅存量維護
SQL 2005-2008 R2@@ERROR + RAISERROR + TRY/CATCH結構化捕捉、自訂訊息、sys.messages 管理廣泛使用
SQL 2012+THROW + TRY/CATCH + sys.messages保留堆疊、語法簡潔、FORMATMESSAGE 格式化**新專案標準**

結語

從 @@ERROR 的易變性、sys.messages 的集中管理、RAISERROR 的靈活拋出、到 TRY/CATCH 的結構化捕捉與 THROW 的堆疊保留,SQL Server 異常處理體系已臻成熟。新專案請採用:**自訂 message_id (sp_addmessage) → 預存程序 THROW/RAISERROR(message_id, 16, 1) → TRY/CATCH 捕捉並 THROW 重拋 → ADO.NET 捕獲 SqlException 依 Number 分流**。並善用 XACT_STATE() 管理交易狀態,構建健壯、可維護、可觀測的資料庫程式碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved