PostgreSQL触发器函数必须声明返回TRIGGER类型,末尾需RETURN NEW/OLD或NULL;NEW/OLD依TG_OP事件可用性不同;禁止手动提交;FOR EACH ROW性能敏感,建议用命名序号管理执行顺序。

触发器函数必须返回 TRIGGER 类型
PostgreSQL 触发器函数不能用普通 plpgsql 函数替代,必须显式声明返回类型为 TRIGGER。否则创建触发器时会报错:ERROR: function must return type trigger。这个约束常被忽略,尤其当开发者复制了普通查询函数稍作修改就拿来用。
实操建议:
- 函数定义开头写
CREATE OR REPLACE FUNCTION func_name() RETURNS TRIGGER AS $$
- 函数体末尾必须有
RETURN NEW;(INSERT/UPDATE)或RETURN OLD;(DELETE),不能漏掉RETURN语句 - 若想跳过原操作(如拒绝插入),可返回
NULL;但需明确这是有意为之,不是遗漏
NEW 和 OLD 在不同事件中是否可用
这两个特殊记录变量不是始终存在:在 INSERT 中只有 NEW 可用,OLD 为 NULL;在 DELETE 中只有 OLD 可用;UPDATE 两者都可用。直接访问 OLD.column_name 而不判断触发事件类型,会导致运行时报错:record "old" has no field "xxx"。
实操建议:
- 用
IF TG_OP = 'UPDATE' THEN ... END IF;显式分情况处理 - 避免无条件写
OLD.updated_at—— 它在INSERT触发时根本不存在 - 若需兼容多事件,可用
COALESCE(NEW.field, OLD.field),但要确认语义合理
触发器函数里调用存储过程要注意事务上下文
触发器函数本身就在当前 DML 事务中执行,它内部调用的任何存储过程(CALL 或 PERFORM 函数)都共享同一事务。这意味着:如果存储过程抛出异常,整个原始 INSERT/UPDATE/DELETE 也会回滚;反之,若触发器函数里做了 COMMIT(比如误用了 autonomous transaction 思路),会直接报错:ERROR: cannot commit while a cursor is open。
实操建议:
- 不要在触发器函数中尝试手动
COMMIT或ROLLBACK - 若需异步或解耦逻辑(如发消息、写日志到另一库),改用
pg_notify()+ 监听进程,或延迟到应用层处理 - 调用其他函数时,确保它们不会隐式提交(例如某些外部扩展的函数可能有副作用)
触发器执行顺序和 FOR EACH ROW 的性能影响
默认触发器是 FOR EACH ROW,对每行执行一次函数。若一个 UPDATE 影响 10 万行,函数就被调用 10 万次 —— 即使函数体只做简单赋值,开销也不容忽视。更隐蔽的问题是:多个触发器共存时,执行顺序由创建顺序决定(pg_trigger.tgrelid 和 pg_trigger.tgname 排序),无法用 AFTER/BEFORE 精确控制依赖关系。
实操建议:
- 批量操作前考虑临时禁用触发器:
ALTER TABLE tbl DISABLE TRIGGER trig_name;(记得恢复) - 若逻辑允许聚合处理(如更新统计表),改用
FOR EACH STATEMENT触发器,但注意此时NEW/OLD不可用,得靠GET DIAGNOSTICS或临时表传数据 - 给触发器命名带序号(如
trig_01_audit,trig_02_sync)便于推断执行顺序
EXPLAIN 都不显示触发器内耗。上线前务必用真实批量数据测响应时间和锁行为。

















