如果你有遇過那種需要對每一行資料做某些動作(像是批次更新),或是只想針對整個表格做一次處理的情境,那你大概會糾結:到底該怎麼實作比較好?PostgreSQL 給你兩種選擇:行級觸發器跟操作級(statement)觸發器。搞懂什麼時候該用哪一種,對資料庫設計、效能優化還有避免踩雷都很重要。來,咱們一起來搞清楚!
所謂行級觸發器(FOR EACH ROW),就是每當 INSERT、UPDATE 或 DELETE 操作影響到一行資料時,就會針對每一行都執行一次。意思是,如果 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 ROW 跟 FOR EACH STATEMENT 的比較
| 比較項目 | FOR EACH ROW | FOR EACH STATEMENT |
|---|---|---|
| 執行層級 | 每一行被動到都執行 | 整個操作只執行一次 |
| 呼叫次數 | 每一行呼叫一次 | 每個 SQL 查詢呼叫一次 |
| 適用任務 | 記錄單筆變動、處理每一行 | 彙總、更新 meta 資訊 |
| 範例 | 每一行變動都記錄 log | 更新變動計數器 |
| 效能 | 大量操作時比較耗資源 | 大量操作時比較省資源 |
什麼時候該用 FOR EACH ROW,什麼時候該用 FOR EACH STATEMENT?
用 FOR EACH ROW,如果:
- 你想讓觸發器針對每一行都執行。
- 你的邏輯需要跟每一行的變動綁在一起。
- 你需要存取每一行的
OLD跟NEW資料。
範例:記錄表格變動 log,或自動建立相關資料。
用 FOR EACH STATEMENT,如果:
- 你只想針對整個操作做一次處理。
- 你的觸發器邏輯跟單一資料列無關。
- 你很在意效能,不想讓觸發器被大量呼叫。
範例:更新計數器、計算表格的 meta 資訊。
常見錯誤跟注意事項
選對觸發器類型有時候不太直觀,這裡有幾個要注意的點:
- 最常見的錯誤之一,就是在
FOR EACH STATEMENT觸發器裡用OLD跟NEW資料。這樣會出錯,因為這兩個變數只有行級觸發器才有。 - 行級觸發器(
FOR EACH ROW)如果一次動到很多行,會讓操作變超慢。記得考慮效能。 - 小心觸發器遞迴。像是觸發器又去改同一張表,可能會造成無限迴圈。
GO TO FULL VERSION