只能选 BEFORE 触发器,因为它是唯一能读写 NEW 并修改待插入/更新值的时机;AFTER 中 NEW 只读,INSTEAD OF 仅适用于视图。

必须用 BEFORE 触发器,且在函数里显式修改 NEW 并返回它;AFTER 或 INSTEAD OF 都做不到“边拦边改”。
为什么只能选 BEFORE?
BEFORE 是唯一能读写 NEW 的时机:INSERT/UPDATE 时可赋值、校验、填充默认值;DELETE 时虽无 NEW,但能通过 RAISE EXCEPTION 拦截。AFTER 运行时数据已落盘,NEW 只读,任何 UPDATE 尝试都会报 ERROR: cannot update table "xxx" in AFTER ROW trigger。INSTEAD OF 只适用于视图,对普通表无效。
常见错误现象:写了 AFTER 函数还试图 UPDATE target_table SET ...,结果触发器直接报错退出,主事务可能回滚或卡住。
怎么改 NEW 字段?
在 PL/pgSQL 函数体中直接赋值,注意 PostgreSQL 用 := 而不是 =:
NEW.created_at := NOW(); NEW.phone := REGEXP_REPLACE(TRANSLATE(NEW.phone, ' -()+', ''), '\D', '', 'g'); IF LENGTH(COALESCE(NEW.phone, '')) != 11 THEN NEW.phone := NULL; END IF;
关键点:
-
NEW是行级变量,只在FOR EACH ROW触发器中可用 - INSERT 里没有
OLD,UPDATE/DELETE 里没有NEW,硬引用会报column "old.id" does not exist - 函数末尾必须写
RETURN NEW(INSERT/UPDATE)或RETURN OLD(DELETE),漏掉就等于没生效 - 不能在 BEFORE 里查本表做复杂判断(如
SELECT COUNT(*) FROM same_table),容易锁表或死锁
如何安全拦截非法写入?
校验失败必须用 RAISE EXCEPTION,不是 RETURN NULL——PostgreSQL 完全不认后者作为中断逻辑:
IF NEW.status NOT IN ('active', 'inactive') THEN
RAISE EXCEPTION 'invalid status: %', NEW.status;
END IF;使用场景和陷阱:
- 想拦住无 WHERE 的批量更新?靠
tg_op = 'UPDATE'+OLD.id IS NOT NULL判断是否为行级触发,再结合业务字段做范围检查 - WHEN 子句比函数内
IF更轻量,但只能写简单表达式,不能调函数或查表:WHEN (NEW.email !~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') - 别在触发器里调外部 HTTP 服务,连接可能失效;要发通知,写入消息队列表,由独立消费者处理
- 函数所属角色必须对涉及的所有表有对应权限(比如往日志表 INSERT),否则主事务会因触发器失败而回滚
容易被忽略的细节
时间戳是 2026 年 8 月 7 日那条提醒依然有效:BEFORE 触发器里改 NEW 字段,改的是即将写入的值,不是原始语句里的字面量——这意味着应用层传入的空字符串、NULL、甚至 SQL 注入后的恶意值,都会被你重写覆盖。 但这也意味着,如果校验逻辑本身有 bug(比如正则写错导致所有手机号变 NULL),问题会静默发生,不会报错。上线前务必用真实脏数据测一遍。

















