PostgreSQL中应使用原生ON DELETE CASCADE,禁用触发器实现级联删除;因其易致锁表、递归溢出、外键冲突;若必须用触发器,须满足禁用外键、FOR EACH STATEMENT、批量删除、防嵌套四条件。

不能安全模拟,也不该模拟。 PostgreSQL 原生的 ON DELETE CASCADE 是唯一推荐方案;触发器实现级联删除是退化设计,容易引发锁表、递归、权限失败或静默漏删。
为什么触发器在PostgreSQL里不适合做级联删除
触发器不是外键的替代品,而是补丁工具。它绕过数据库内建的完整性保障机制,带来三类硬伤:
- 事务锁范围失控:一个
BEFORE DELETE FOR EACH ROW触发器删 3 张子表,批量删 1000 行用户,就会执行 3000 次 DELETE,极大概率锁住整个users表,阻塞其他查询 - 嵌套深度不可控:若子表
orders也定义了删order_items的触发器,DELETE FROM users可能触发多层递归,最终报stack depth limit exceeded - 外键检查与触发器打架:你在触发器里手动
DELETE FROM orders WHERE user_id = OLD.id,但此时users行还没真正删掉,外键约束仍生效,可能直接报错update or delete on table "users" violates foreign key constraint
如果非要用触发器,必须满足这四个条件
仅限极特殊场景(如需动态判断是否级联、或子表无外键权限),且必须同时满足:
- 外键必须先禁用
ON DELETE CASCADE,否则触发器根本不会被调用 - 触发器类型必须是
BEFORE DELETE FOR EACH STATEMENT(不是FOR EACH ROW),否则高并发下性能崩盘 - 触发器函数内必须用
array_agg(OLD.id)批量提取待删主键,再用IN (SELECT ...)一次性删子表,避免逐行扫描 - 函数开头加
IF pg_trigger_depth() > 1 THEN RETURN; END IF;防止意外嵌套
更稳妥的替代路径
别写触发器,改用数据库原生能力:
- 对已有表补外键:先查外键名
SELECT conname FROM pg_constraint WHERE confrelid = 'users'::regclass,再ALTER TABLE orders DROP CONSTRAINT xxx; ALTER TABLE orders ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; - 批量清理深层子表(如
user_logs)?用TRUNCATE ... CASCADE——但它要求所有外键都带ON DELETE CASCADE,且不走触发器、不发通知、重置序列 - 需要条件清理(比如只删 30 天前的日志)?别塞进主删除链,改用异步 job 或定时
DELETE FROM user_logs WHERE user_id IN (...) AND created_at
最容易被忽略的一点:即使你把触发器写得滴水不漏,只要没给触发器定义者(DEFINER)授予子表的 DELETE 权限,它就会静默跳过——连错误都不报。而 ON DELETE CASCADE 完全不依赖用户权限,只要约束存在就生效。

















