CodeGym /課程 /SQL SELF /開發 trigger 時常見錯誤分析

開發 trigger 時常見錯誤分析

SQL SELF
等級 58 , 課堂 4
開放

開發 trigger 時常見錯誤分析

好啦各位同學,你們現在已經知道 trigger 是什麼、有哪幾種、怎麼運作,甚至已經會寫 trigger 來做各種事。但寫 code 跟 debug 一樣重要,知道「不能怎麼做」有時比「能怎麼做」還重要。今天我們就來聊聊大家在寫 trigger 時最容易踩到的雷,讓你少花幾個小時,甚至幾天在 debug 上面。

Trigger 遞迴:trigger 自己叫自己

這大概是新手最常犯的錯。想像一下,你寫了一個 trigger,會去更新某個欄位,比如 last_modified。但只要這個欄位一被改,update 動作又會再觸發 trigger。這樣就會無限循環,最後 server 直接爆掉,stack overflow error。

範例:

CREATE OR REPLACE FUNCTION update_last_modified()
RETURNS TRIGGER AS $$
BEGIN
    -- 更新 last_modified 欄位
    UPDATE my_table
    SET last_modified = NOW()
    WHERE id = NEW.id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER after_update
AFTER UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_last_modified();

這裡哪裡怪怪的?function 裡的 UPDATE 會再觸發同一個 trigger,然後就無限 loop 了。

怎麼避免:

OLD 變數,先比對值有沒有變再決定要不要動手:

CREATE OR REPLACE FUNCTION update_last_modified_safe()
RETURNS TRIGGER AS $$
BEGIN
    -- 檢查值有沒有變
    IF NEW.last_modified IS DISTINCT FROM OLD.last_modified THEN
        NEW.last_modified = NOW();
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

記得不要在 trigger 裡做多餘的操作。

錯誤使用 OLDNEW

這兩個變數是 trigger 好朋友,但新手常常搞混。OLD 是改動前的資料,NEW 是改動後要存進去的資料。

常見錯誤是搞錯什麼時候可以用,或是用在不能用的地方。比如 BEFORE INSERT trigger,OLD 根本不存在,因為資料還沒進來。

錯誤範例:

-- 這會出錯,因為 insert 時沒有 OLD
CREATE OR REPLACE FUNCTION log_inserts()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO audit_log (old_data, new_data)
    VALUES (OLD.my_column, NEW.my_column);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

怎麼避免:

記得什麼時候該用 OLD、什麼時候該用 NEW

  • OLD 只有 UPDATEDELETE 用得到。
  • NEW 只有 INSERTUPDATE 用得到。

同一操作有多個 trigger

PostgreSQL 允許你在同一個 table、同一個操作上設多個 trigger。看起來很方便,但如果 trigger 互相打架或改到同一份資料,結果就會很亂。

範例:

-- Trigger 1
CREATE OR REPLACE FUNCTION trigger_one()
RETURNS TRIGGER AS $$
BEGIN
    -- trigger 1 的邏輯
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger 2
CREATE OR REPLACE FUNCTION trigger_two()
RETURNS TRIGGER AS $$
BEGIN
    -- trigger 2 的邏輯
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 建立兩個 trigger
CREATE TRIGGER trigger_one AFTER INSERT ON my_table EXECUTE FUNCTION trigger_one();
CREATE TRIGGER trigger_two AFTER INSERT ON my_table EXECUTE FUNCTION trigger_two();

兩個 trigger 都會在 my_table insert 時被叫到。如果邏輯沒協調好,結果就會很難預期。

怎麼避免:

  • 一開始就規劃好 trigger 架構。
  • 如果邏輯很像,乾脆合併成一個 trigger。

效能問題

trigger 會讓每次操作多做一些事。如果你在大 table 或高頻率操作上用 trigger,效能可能會掉很多。

錯誤範例:

CREATE OR REPLACE FUNCTION heavy_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
    -- 每次更新都做很重的事
    PERFORM some_heavy_query();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER performance_killer AFTER UPDATE ON huge_table EXECUTE FUNCTION heavy_trigger_function();

怎麼避免:

  • trigger 裡的邏輯越簡單越好。真的要做重的事,考慮丟到 background job。
  • WHEN 條件,限制 trigger 什麼時候才執行:
CREATE TRIGGER optimized_trigger
AFTER UPDATE ON my_table
WHEN (OLD.column_name IS DISTINCT FROM NEW.column_name)
EXECUTE FUNCTION light_function();

Trigger 跟 transaction

trigger 會在你 query 開的 transaction 裡執行。如果 trigger 出錯,整個 transaction 都會 rollback。有時這很有用,但如果沒想清楚怎麼處理錯誤,可能會出大事。

錯誤範例:

CREATE OR REPLACE FUNCTION error_prone_trigger()
RETURNS TRIGGER AS $$
BEGIN
    -- 故意丟錯
    RAISE EXCEPTION '有東西壞掉啦!';
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

這個 trigger 只要被叫到,你的主 query 就會被 rollback。

怎麼避免:

在 trigger 裡加錯誤處理,減少對主 transaction 的影響:

CREATE OR REPLACE FUNCTION safe_trigger()
RETURNS TRIGGER AS $$
BEGIN
    BEGIN
        -- 可能會出錯的 code
        INSERT INTO another_table VALUES (NEW.data);
    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE '有錯誤發生,不過我們有處理好。';
    END;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

實用建議

  1. trigger 越簡單越好。 如果你覺得 trigger 太大太複雜,應該拆成幾個 function 或重想邏輯。

  2. 先在小資料量測試 trigger。 不要一開始就丟到重要 table,先在測試環境玩玩看。

  3. 記得寫 trigger 文件。 幾個月後你或同事一定會忘記這 trigger 幹嘛的。寫清楚省很多麻煩。

  4. 能在 application 層解決的事就不要用 trigger。 trigger 很適合自動化即時處理,但複雜的 business logic 用 trigger 以後會很難維護。

  5. 注意效能。 隨時監控 trigger 對資料庫效能的影響,尤其是資料量或流量變大時。

有了這些 tips 跟今天學到的東西,你不只會寫 trigger,還能寫出又正確又有效率、不會出奇怪 bug 的 trigger!

1
問卷/小測驗
行級與表級觸發器,等級 58,課堂 4
未開放
行級與表級觸發器
行級與表級觸發器
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION