CodeGym /課程 /SQL SELF /在觸發器中處理錯誤與交易:EXCEPTION、RAISE

在觸發器中處理錯誤與交易:EXCEPTION、RAISE

SQL SELF
等級 58, 課堂 3
開放

有時候觸發器會出現一些很難預料的狀況,這通常跟以下幾個原因有關:

  • 觸發器綁定的 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 訊息類型:

  1. DEBUG — 除錯訊息。
  2. NOTICE — 一般資訊訊息。
  3. WARNING — 警告。
  4. 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。解法:讓觸發器邏輯越簡單越好,避免太重的查詢。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION