CodeGym /課程 /SQL SELF /行級與表級觸發器:FOR EACH ROW vs FOR EACH STATEMENT

行級與表級觸發器:FOR EACH ROW vs FOR EACH STATEMENT

SQL SELF
等級 58 , 課堂 2
開放

如果你有遇過那種需要對每一行資料做某些動作(像是批次更新),或是只想針對整個表格做一次處理的情境,那你大概會糾結:到底該怎麼實作比較好?PostgreSQL 給你兩種選擇:行級觸發器跟操作級(statement)觸發器。搞懂什麼時候該用哪一種,對資料庫設計、效能優化還有避免踩雷都很重要。來,咱們一起來搞清楚!

所謂行級觸發器FOR EACH ROW),就是每當 INSERTUPDATEDELETE 操作影響到一行資料時,就會針對每一行都執行一次。意思是,如果 SQL 查詢動到 100 行,這個觸發器就會跑 100 次。

什麼時候該用?

行級觸發器很適合你需要針對每一筆被改動的資料單獨處理的時候。舉例來說:

  • 每一行變動都要記錄 log。
  • 每一行都要自動更新相關資料。

範例:每一行變動都要記錄 log

假設我們有個學生表:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    age INT,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

我們想把每次這個表有資料被更新時,都記錄到另一個 students_log 表:

CREATE TABLE students_log (
    log_id SERIAL PRIMARY KEY,
    student_id INT,
    old_name VARCHAR(100),
    new_name VARCHAR(100),
    changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

這是用來記錄變動的 function:

CREATE OR REPLACE FUNCTION log_student_update()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO students_log(student_id, old_name, new_name, changed_at)
    VALUES (OLD.id, OLD.name, NEW.name, CURRENT_TIMESTAMP);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

建立 FOR EACH ROW 觸發器:

CREATE TRIGGER student_update_logger
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION log_student_update();

測試一下:

UPDATE students
SET name = '伊凡 伊凡諾夫'
WHERE id = 1;

執行完這個查詢後,students_log 表裡就會多一筆詳細的變動紀錄。

操作級觸發器(FOR EACH STATEMENT

操作級觸發器(FOR EACH STATEMENT)不管你這個 SQL 查詢動到幾行,只會執行一次。就算你一次更新 100 行,這個觸發器也只跑一次。

操作級觸發器適合這些情境:

  • 你只想針對整個操作做一次處理。
  • 你要處理彙總資料或是對整個表做計算。

範例:更新變動計數器

假設我們有個用來記錄 students 表變動次數的表:

CREATE TABLE students_changes_log (
    total_changes INT DEFAULT 0
);
INSERT INTO students_changes_log(total_changes) VALUES (0);

我們想要每次 students 表被 UPDATE 時,這個計數器就加一。

這是更新計數器的 function:

CREATE OR REPLACE FUNCTION increment_changes_counter()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE students_changes_log
    SET total_changes = total_changes + 1;
    RETURN NULL; -- 操作級觸發器不用回傳資料列
END;
$$ LANGUAGE plpgsql;

建立 FOR EACH STATEMENT 觸發器:

CREATE TRIGGER update_changes_counter
AFTER UPDATE ON students
FOR EACH STATEMENT
EXECUTE FUNCTION increment_changes_counter();

測試一下:

UPDATE students
SET age = age + 1
WHERE age < 20;

執行這個查詢後,觸發器只會跑一次,計數器就會加一。

FOR EACH ROWFOR EACH STATEMENT 的比較

比較項目 FOR EACH ROW FOR EACH STATEMENT
執行層級 每一行被動到都執行 整個操作只執行一次
呼叫次數 每一行呼叫一次 每個 SQL 查詢呼叫一次
適用任務 記錄單筆變動、處理每一行 彙總、更新 meta 資訊
範例 每一行變動都記錄 log 更新變動計數器
效能 大量操作時比較耗資源 大量操作時比較省資源

什麼時候該用 FOR EACH ROW,什麼時候該用 FOR EACH STATEMENT

FOR EACH ROW,如果:

  1. 你想讓觸發器針對每一行都執行。
  2. 你的邏輯需要跟每一行的變動綁在一起。
  3. 你需要存取每一行的 OLDNEW 資料。

範例:記錄表格變動 log,或自動建立相關資料。

FOR EACH STATEMENT,如果:

  1. 你只想針對整個操作做一次處理。
  2. 你的觸發器邏輯跟單一資料列無關。
  3. 你很在意效能,不想讓觸發器被大量呼叫。

範例:更新計數器、計算表格的 meta 資訊。

常見錯誤跟注意事項

選對觸發器類型有時候不太直觀,這裡有幾個要注意的點:

  1. 最常見的錯誤之一,就是在 FOR EACH STATEMENT 觸發器裡用 OLDNEW 資料。這樣會出錯,因為這兩個變數只有行級觸發器才有。
  2. 行級觸發器(FOR EACH ROW)如果一次動到很多行,會讓操作變超慢。記得考慮效能。
  3. 小心觸發器遞迴。像是觸發器又去改同一張表,可能會造成無限迴圈。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION