必须用raise exception才能拦截delete,其他方式如raise notice、return null均无效;条件判断需显式处理空值,优先使用when子句,函数须设security definer并实测。

PostgreSQL的BEFORE DELETE触发器必须用RAISE EXCEPTION才能真正拦截
仅在触发器函数里写IF OLD.status = 'active' THEN ... END IF但不抛异常,DELETE照常执行。PostgreSQL不会因为条件判断就自动中止语句——它只认RAISE EXCEPTION这一种“掐断”方式。
-
RAISE NOTICE或RAISE WARNING只会打日志,删操作继续跑 -
RETURN NULL在BEFORE触发器里只是让当前行跳过后续逻辑,不阻止删除(甚至可能引发意外行为) - 必须用
RAISE EXCEPTION,它会立即终止当前事务,回滚整个DELETE语句
怎么写带条件的RAISE EXCEPTION才安全可靠
直接在RAISE EXCEPTION前加IF判断是最稳的做法,避免空值、类型转换失败等导致校验被跳过。
- 字段值为空时
OLD.role IS NULL要显式处理,别依赖隐式比较 - 不要在触发器里查其他大表(比如
SELECT count(*) FROM audit_log WHERE ...),否则每次DELETE都变慢,线上扛不住 - 如果条件涉及时间范围或状态字段,优先用
WHEN子句(PG 12+支持),比在函数体里写IF更轻量:例如CREATE TRIGGER tr_block_active_del BEFORE DELETE ON config FOR EACH ROW WHEN (OLD.enabled = true) EXECUTE FUNCTION block_delete()
批量DELETE里只要一行触发EXCEPTION,整条语句就失败
这是PG的事务行为,不是bug。比如DELETE FROM users WHERE id IN (101, 102, 103),只要其中id = 102那行满足触发条件,整个语句报错,三行都删不掉。
- 这适合防误删,但会卡住合法归档脚本——得靠上下文区分:比如检查
current_setting('app.session_type', true)是否为'archive'再决定是否放行 - 别在触发器里调用
pg_backend_pid()或current_user做简单白名单,容易被绕过;真要用,得配合应用层传入可信会话变量 - 错误信息里尽量包含关键字段值,方便排查:
RAISE EXCEPTION 'Cannot delete user % with role %', OLD.id, OLD.role
触发器函数必须标记SECURITY DEFINER且权限足够
函数若用到current_setting()、查系统表或调用自定义函数,而创建者是普通用户,运行时可能因权限不足静默失败或报permission denied。
- 创建时加
SECURITY DEFINER:CREATE OR REPLACE FUNCTION block_delete() RETURNS trigger SECURITY DEFINER AS $$ ... $$ LANGUAGE plpgsql - 确保该函数所有依赖对象(比如被查的配置表)对执行用户可读
- 别把复杂权限逻辑塞进触发器——先收窄表级DML权限,再用触发器补业务规则,两层防线更靠谱
RAISE EXCEPTION那一行,其余都是为它铺路。写完务必用DELETE语句实测,别只看函数编译通过。










