触发器函数必须返回TRIGGER类型,否则创建时报错;须用format('%I')安全拼接标识符;NEW/OLD需依TG_OP分支访问;FOR EACH ROW在批量操作中性能开销巨大。

触发器函数必须返回 TRIGGER 类型
PostgreSQL 14 不允许用 RETURNS void 或 RETURNS text 的函数当触发器——创建时直接报错:ERROR: function must return type trigger。这不是警告,是硬性类型检查。
实操要点:
- 函数定义开头必须写
RETURNS TRIGGER(大小写不敏感,但推荐全大写) - 函数体末尾必须有
RETURN NEW、RETURN OLD或RETURN NULL,不能遗漏 -
RETURN NEW用于BEFORE INSERT/UPDATE,让修改后的行继续执行原操作 -
RETURN NULL表示跳过当前行(常用于校验拦截),RETURN OLD多用于BEFORE DELETE
动态 SQL 拼接表名/字段名必须用 format('%I', ...)
在触发器函数里写 EXECUTE 'INSERT INTO ' || TG_TABLE_NAME || ' ...' 是错误的:PostgreSQL 会把 TG_TABLE_NAME 当作未定义变量或语法错误处理,更严重的是引入 SQL 注入风险。
正确做法是用 format() + %I 占位符:
-
%I自动加双引号并转义,把任意输入当作合法标识符(表名、列名、schema 名) -
%L用于字符串/数值字面量,等价于quote_literal(),自动处理NULL和单引号 - 禁止混用:
EXECUTE format('SELECT %I FROM %I', 'name', TG_TABLE_NAME) USING ...——USING只能传值,不能传标识符
NEW 和 OLD 必须按 TG_OP 分支访问
在 INSERT 触发器里读 OLD.id,或在 DELETE 里读 NEW.created_at,运行时报错:record "old" has no field "id"。PostgreSQL 14 不做静默兼容,而是严格按事件暴露记录变量。
实操建议:
- 用
TG_OP显式分支:IF TG_OP = 'UPDATE' THEN ... END IF; - 避免无条件引用:
OLD.updated_at只在UPDATE和DELETE中有效 - 需要统一取值时,可用
COALESCE(NEW.field, OLD.field),但要确认语义合理(比如审计时间字段)
FOR EACH ROW 触发器性能开销远超直觉
PostgreSQL 14 默认是 FOR EACH ROW,一条影响 50 万行的 UPDATE 就会调用触发器函数 50 万次。哪怕函数体只有一行赋值,开销也远超预期——特别是里面还嵌套了 EXECUTE、JSON 构造或跨 schema 函数调用。
关键细节:
- 多个触发器共存时,执行顺序由创建时间决定(
pg_trigger.tgrelid + pg_trigger.tgname排序),无法靠BEFORE/AFTER控制依赖 - 若逻辑可批量处理(如日志归档),优先考虑
FOR EACH STATEMENT+ 手动查变更行,而非逐行触发 - 测试时别只用单行
INSERT验证功能,一定要压测真实批量场景
pg_stat_activity 中出现长时间运行的会话。写之前先想清楚——这逻辑真必须每行都跑一遍吗?

















