有時候觸發器會出現一些很難預料的狀況,這通常跟以下幾個原因有關:
- 觸發器綁定的 function 裡有邏輯錯誤。
- 資料庫限制被破壞(像是唯一性違反或資料型別不符)。
- 交易出問題,觸發器因為錯誤導致變更被 rollback。
- 遞迴,像是觸發器不小心自己呼叫自己(常常是不小心的)。
為了避免這些問題,PostgreSQL 讓你可以在觸發器和 function 裡處理錯誤。這些工具包含 EXCEPTION 區塊和 RAISE 指令,今天我們會用範例來說明。
用 EXCEPTION 區塊處理錯誤
EXCEPTION 區塊可以讓你攔截錯誤,然後執行一些處理的程式碼。這有點像 Python 或 Java 裡的 try-catch。
EXCEPTION 區塊在 PL/pgSQL function 裡的用法如下:
BEGIN
-- function 的主要程式碼
EXCEPTION
WHEN <錯誤類型> THEN
-- 處理錯誤的程式碼
END;
這裡的 <錯誤類型> 就是你想處理的特定錯誤或錯誤群組(像是 unique_violation、division_by_zero 等等)。
範例:在觸發器裡記錄錯誤
假設我們有一個 logs 表格,要把插入 students 表時發生的錯誤記錄下來。範例如下:
建立 log 表格
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
error_message TEXT
);
建立有錯誤處理的 function
CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
-- 嘗試執行主要程式碼
BEGIN
-- 範例「錯誤」動作:除以 0
PERFORM 1 / (NEW.some_value - NEW.some_value);
EXCEPTION
WHEN division_by_zero THEN
-- 如果發生除以 0 的錯誤,把它寫進 log
INSERT INTO logs (error_message) VALUES ('插入 students 時發生除以 0 的錯誤');
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
建立觸發器
CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();
現在,如果你在 students 表插入資料時發生除以零的錯誤,它會被處理掉,然後錯誤資訊會寫進 logs 表。
用 RAISE 做診斷和除錯
RAISE 指令可以讓你輸出警告、錯誤或除錯資訊。這超級好用,尤其是你想搞懂你的觸發器到底怎麼運作(或是為什麼沒運作!)的時候。
RAISE 訊息類型:
DEBUG— 除錯訊息。NOTICE— 一般資訊訊息。WARNING— 警告。EXCEPTION— 錯誤訊息,會讓 function 終止。
RAISE 語法:
RAISE <訊息類型> '訊息內容';
你也可以帶變數值:
RAISE NOTICE 'NEW.id 的值 = %', NEW.id;
範例:在觸發器裡除錯變數值
假設我們在更新 students 表時遇到錯誤,想知道 NEW 跟 OLD 的值是什麼。這時可以用 RAISE:
CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;
-- 範例條件,會產生錯誤:
IF NEW.some_field IS NULL THEN
RAISE EXCEPTION 'some_field 欄位不能是 NULL';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();
現在每次更新資料時,你都可以看到 OLD 跟 NEW 的值,如果有錯誤也會有清楚的錯誤訊息。
觸發器裡的交易
觸發器是在交易(transaction)裡執行的。意思是,只要觸發器或它的 function 裡有錯誤,整個交易都會被 rollback。這可以很優雅地保護資料庫不會只改一半。
但這種行為有時候也會帶來麻煩:
- 如果觸發器裡的錯誤只是資料有問題,有時候你只想 rollback 部分動作。
- 要記得,交易 rollback 不只包含觸發器,還有觸發它的整個操作。
範例:在觸發器裡用交易
舉例來說,假設我們要做一些商業邏輯,包含兩個動作:更新 students 表和寫 log 到 logs。如果其中一個失敗,整個交易都會 rollback。
CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
-- 記錄更新嘗試
INSERT INTO logs (error_message) VALUES ('嘗試更新 id 為 ' || NEW.id || ' 的學生');
-- 檢查商業條件
IF NEW.some_value IS NULL THEN
RAISE EXCEPTION 'some_value 欄位不能是 NULL';
END IF;
-- 如果都沒問題,回傳 NEW
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();
觸發器常見錯誤與避免方法
開發者常遇到的錯誤:
遞迴觸發器。 就是觸發器改了資料又再度觸發自己。解法:用 WHEN 條件或加 flag 來避免重複呼叫。
因錯誤導致整個交易 rollback。 有時候這不是你想要的,特別是觸發器跟主要資料沒直接關係時。解法:聰明用 EXCEPTION 區塊。
太多除錯資訊。 log 太多會讓分析變難。解法:只在開發和測試時用 RAISE。
效能下降。 複雜的觸發器會拖慢 INSERT、UPDATE 或 DELETE。解法:讓觸發器邏輯越簡單越好,避免太重的查詢。
GO TO FULL VERSION