在編寫儲存程序、觸發器或複雜事務時,優雅地處理錯誤至關重要。MySQL 提供了 DECLARE HANDLER 機制,可針對特定錯誤碼、SQLSTATE 或條件類型定義處理邏輯。本文詳細解析各類處理器語法與實戰模式。
DECLARE {CONTINUE | EXIT} HANDLER FOR condition_value
statement;
BEGIN...END 程序塊(或整個儲存程序)。| 形式 | 範例 | 適用場景 |
|---|---|---|
| MySQL 錯誤碼 | 1062 (重複鍵)、1329 (游標無數據) | 針對特定 MySQL 原生錯誤 |
| SQLSTATE 代碼 | '23000' (完整性約束違規)、'02000' (無數據) | 標準 SQL 狀態,跨資料庫可移植 |
| 命名條件 | SQLEXCEPTION、SQLWARNING、NOT FOUND | 涵蓋一類錯誤的通用處理 |
NOT FOUND 外),如語法錯誤、約束違規、權限不足等。SELECT ... INTO、游標 FETCH 未返回行時觸發(SQLSTATE '02000')。DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
SET l_error = 1;
適用於批量處理中,單條失敗不影響整體流程。
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Error occurred – terminating' AS msg;
END;
事務場景必備,確保數據一致性。
-- 方式一:MySQL 錯誤碼
DECLARE CONTINUE HANDLER FOR 1062
SELECT 'Duplicate key in index' AS msg;
-- 方式二:SQLSTATE(推薦,標準化)
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000'
SELECT 'Duplicate key in index' AS msg;
-- 命名條件
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET l_done = 1;
-- 等價 SQLSTATE
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000'
SET l_done = 1;
-- 等價 MySQL 錯誤碼 1329
DECLARE CONTINUE HANDLER FOR 1329
SET l_done = 1;
常用於游標循環結束判斷。
BEGIN...END 塊內有效。巢狀塊可覆蓋外層處理器。SQLSTATE > MySQL 錯誤碼 > 命名條件。精確匹配優先。GET DIAGNOSTICS 獲取詳細錯誤碼、訊息等。
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
@errno = MYSQL_ERRNO, @msg = MESSAGE_TEXT;
INSERT INTO error_log (errno, msg) VALUES (@errno, @msg);
END;
DELIMITER //
CREATE PROCEDURE transfer_funds(
IN from_acct INT, IN to_acct INT, IN amount DECIMAL(10,2)
)
BEGIN
DECLARE l_error TINYINT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET l_error = 1;
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET l_error = 2; -- 重複鍵等
START TRANSACTION;
UPDATE accounts SET balance = balance - amount WHERE id = from_acct;
UPDATE accounts SET balance = balance + amount WHERE id = to_acct;
INSERT INTO transfer_log (from_acct, to_acct, amount) VALUES (from_acct, to_acct, amount);
IF l_error = 1 THEN
ROLLBACK;
SELECT 'System error, rolled back' AS result;
ELSEIF l_error = 2 THEN
ROLLBACK;
SELECT 'Constraint violation, rolled back' AS result;
ELSE
COMMIT;
SELECT 'Success' AS result;
END IF;
END//
DELIMITER ;