如果你遇到过那种批量更新数据时,想对每一行都做点啥,或者只想对整个表操作一次的场景,那你肯定纠结过:到底该怎么实现?PostgreSQL给了你两种选择:行级触发器和语句级触发器。搞明白啥时候用哪个,对数据库设计、性能优化和避免踩坑都很重要。咱们来详细聊聊!
那些行级触发器(FOR EACH ROW)会在每一行被INSERT、UPDATE或DELETE操作影响时触发一次。也就是说,如果SQL语句影响了100行,这个触发器就会被执行100次。
啥时候用它们?
行级触发器适合你需要单独处理每一行变化的时候。比如:
- 给每一行的变更都做日志记录。
- 每一行自动更新相关数据。
例子:每行变更日志
假设我们有个学生表:
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
);
记录变更的函数:
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);
我们想每次UPDATE操作都让计数器加1。
更新计数器的函数:
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;
执行完这个语句后,触发器只会执行一次,计数器加1。
FOR EACH ROW和FOR EACH STATEMENT对比
| 对比项 | FOR EACH ROW | FOR EACH STATEMENT |
|---|---|---|
| 执行级别 | 每一行被影响时 | 整个操作只一次 |
| 触发频率 | 每行一次 | 每个SQL语句一次 |
| 适用场景 | 单独变更日志、行处理 | 聚合、元数据更新 |
| 例子 | 每行变更日志 | 变更计数器更新 |
| 性能 | 批量操作时开销大 | 批量操作时开销小 |
啥时候用FOR EACH ROW,啥时候用FOR EACH STATEMENT?
用FOR EACH ROW,如果:
- 你想让触发器对每一行都执行。
- 你的逻辑和具体行的变化有关。
- 你需要访问每行的
OLD和NEW数据。
例子:表变更日志,或者自动生成相关记录。
用FOR EACH STATEMENT,如果:
- 你只想对整个操作执行一次。
- 触发器逻辑和具体哪行变了没关系。
- 性能很重要,不想让触发器被大量调用。
例子:计数器更新,表的元数据计算。
常见错误和注意点
选对触发器类型其实没那么直观,这里有些要注意的:
- 最常见的坑就是在
FOR EACH STATEMENT触发器里用OLD和NEW数据。这会报错,因为这俩变量只有行级触发器能用。 - 行级触发器(
FOR EACH ROW)如果遇到大批量操作,可能会让性能暴跌。一定要考虑性能问题。 - 小心触发器递归。比如触发器又改了同一个表的数据,可能会死循环。
GO TO FULL VERSION