PostgreSQL 15 中语句级触发器必须显式声明 FOR EACH STATEMENT 且函数必须返回 NULL;不可使用 NEW/OLD,误写 RETURN NEW 或漏写 RETURN NULL 会导致执行失败,且 CREATE TRIGGER 必须明确指定触发级别。

PostgreSQL 15 中语句级触发器必须显式声明 FOR EACH STATEMENT,且触发器函数必须返回 NULL —— 漏写 RETURN NULL 会导致执行失败。
语句级触发器的函数必须返回 NULL
语句级触发器不处理单行数据,因此不能用 NEW 或 OLD,也不应返回 NEW 或 OLD。它的唯一合法返回值是 NULL,且必须显式写出。
-
RETURN NULL是强制要求,不是可选;隐式无返回会被 PostgreSQL 当作RETURN NULL处理,但部分版本(如 15.6+)在严格模式下会报错或行为不一致 - 若误写
RETURN NEW,插入/更新语句会直接失败,并提示ERROR: record "new" is not assigned - 函数返回类型仍需声明为
TRIGGER,但内部逻辑不依赖行变量
CREATE TRIGGER 语法中必须指定 FOR EACH STATEMENT
仅靠函数逻辑无法决定触发级别,必须在 CREATE TRIGGER 语句中明确标注,否则默认是行级(FOR EACH ROW)。
- 错误写法:
CREATE TRIGGER log_after_insert AFTER INSERT ON users EXECUTE FUNCTION log_statement()→ 默认行级,函数里没NEW就崩 - 正确写法:
CREATE TRIGGER log_after_insert AFTER INSERT ON users FOR EACH STATEMENT EXECUTE FUNCTION log_statement() -
FOR EACH STATEMENT和FOR EACH ROW不能共存;同一事件上可同时定义多个触发器,但级别必须分开声明
典型使用场景:批量操作审计、统计更新、权限拦截
语句级适合不关心“改了哪几行”,只关心“有没有执行这个动作”的场景。
- 记录一次
DELETE FROM logs WHERE ts 的执行时间,而不是逐条记日志 - 在
UPDATE语句后刷新物化视图:REFRESH MATERIALIZED VIEW stats_summary - 拦截高危语句:用
event_trigger更合适,但普通语句级触发器可用于表级限制,例如禁止空条件的UPDATE:IF TG_OP = 'UPDATE' AND NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = TG_NAME) THEN RAISE EXCEPTION 'Unsafe UPDATE without WHERE'; END IF;(注意:实际判断需查pg_stat_statements或解析TG_TAG,此处仅为示意逻辑位置)
容易被忽略的兼容性细节
PostgreSQL 15 对语句级触发器的行为做了更严格的校验,尤其在事务嵌套和触发器链中。
-
TRUNCATE只支持语句级触发器,不支持行级;TRUNCATE ... CASCADE会分别触发各表上的语句级触发器 - 如果一个语句同时触发多个语句级触发器(比如两个
AFTER INSERT),它们按触发器名字母序执行,而非创建顺序 - 函数内调用
RAISE EXCEPTION会中止整个语句,且已执行的修改(如前面的INSERT)会回滚 —— 这点和行级不同,行级可对某几行跳过而不影响其他行

















