CodeGym /课程 /SQL SELF /行级和表级触发器:FOR EACH ROW vs FOR EACH STATEMENT

行级和表级触发器:FOR EACH ROW vs FOR EACH STATEMENT

SQL SELF
第 58 级 , 课程 2
可用

如果你遇到过那种批量更新数据时,想对每一行都做点啥,或者只想对整个表操作一次的场景,那你肯定纠结过:到底该怎么实现?PostgreSQL给了你两种选择:行级触发器和语句级触发器。搞明白啥时候用哪个,对数据库设计、性能优化和避免踩坑都很重要。咱们来详细聊聊!

那些行级触发器(FOR EACH ROW)会在每一行INSERTUPDATEDELETE操作影响时触发一次。也就是说,如果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 ROWFOR EACH STATEMENT对比

对比项 FOR EACH ROW FOR EACH STATEMENT
执行级别 每一行被影响时 整个操作只一次
触发频率 每行一次 每个SQL语句一次
适用场景 单独变更日志、行处理 聚合、元数据更新
例子 每行变更日志 变更计数器更新
性能 批量操作时开销大 批量操作时开销小

啥时候用FOR EACH ROW,啥时候用FOR EACH STATEMENT

FOR EACH ROW,如果:

  1. 你想让触发器对每一行都执行。
  2. 你的逻辑和具体行的变化有关。
  3. 你需要访问每行的OLDNEW数据。

例子:表变更日志,或者自动生成相关记录。

FOR EACH STATEMENT,如果:

  1. 你只想对整个操作执行一次。
  2. 触发器逻辑和具体哪行变了没关系。
  3. 性能很重要,不想让触发器被大量调用。

例子:计数器更新,表的元数据计算。

常见错误和注意点

选对触发器类型其实没那么直观,这里有些要注意的:

  1. 最常见的坑就是在FOR EACH STATEMENT触发器里用OLDNEW数据。这会报错,因为这俩变量只有行级触发器能用。
  2. 行级触发器(FOR EACH ROW)如果遇到大批量操作,可能会让性能暴跌。一定要考虑性能问题。
  3. 小心触发器递归。比如触发器又改了同一个表的数据,可能会死循环。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION