BEFORE DELETE,AFTER 会直接报错 ERROR 1442——为什么?因为 MySQL 在 AFTER DELETE 触发器中禁止对原表做任何读写(包括 SELECT 或 JOIN),虽然 OLD.id 还能访问,但要定位子表关联数据时,如果字段类型不一致,很容易漏删。所以,BEFORE DELETE 是唯一可靠的选择,它能拿到 OLD.id 去执行 DELETE FROM child WHERE parent_id = OLD.id。

MySQL 必须用 BEFORE DELETE,AFTER 会报 ERROR 1442
先说几个关键点:
BEFORE DELETE才能可靠拿到OLD.id,并执行清理子表的操作。- 写触发器时,记得用
DELIMITER $$包裹整个体,否则分号会让 MySQL 以为语句结束了。 - 触发器内部不能写
COMMIT或START TRANSACTION,MySQL 不允许显式事务控制。 - 一个很实用的提醒:如果你的子表已经有
ON DELETE CASCADE外键,那这个触发器是多余的——先查清楚约束状态再说。
PostgreSQL 优先用外键 CASCADE,触发器只用于复杂逻辑
PostgreSQL 原生支持 ON DELETE CASCADE,性能好、原子性强、行为可预测。触发器(比如 BEFORE DELETE 加一个函数)只有在需要条件判断、记录日志或跨 schema 清理时,才值得考虑。
- 建外键时,必须显式写
ON DELETE CASCADE,光写REFERENCES是不够的。 - 如果子表里已有数据,添加约束前要确保所有
parent_id都指向真实父记录,否则ADD CONSTRAINT会失败,并提示violates foreign key constraint。 - 级联深度理论上没有硬性限制,但嵌套超过三层(比如 A→B→C→D)时,
DELETE FROM A可能会锁表秒级,需要根据业务判断是否可接受。
SQL Server 的 INSTEAD OF 触发器必须手动删父表
SQL Server 的 INSTEAD OF DELETE 会完全接管删除动作,但有个常见的坑:它不会自动删除父表本身。你必须在触发器里显式写 DELETE FROM parent_table WHERE id IN (SELECT id FROM deleted),否则看起来“级联”了,但父表还在。
deleted是一个只读临时表,仅在触发器内可见,里面存的是本次即将被删的行。- 如果子表也有
INSTEAD OF触发器,需要确认是否开启了嵌套触发器:用sp_configure 'nested triggers', 1检查。 - 多个子表清理时,建议用
JOIN或分步DELETE,避免单条语句锁表太久。
真正麻烦的不是写法的对错,而是那些容易被忽略的细节——递归调用、隐式类型转换。比如主表 id 是 BIGINT UNSIGNED,子表 parent_id 是 INT,MySQL 会隐式转成有符号整型,导致索引失效、漏删;再比如父子表之间有双向外键,触发器一触发就陷入死循环。这些坑,不跑通测试很难发现。
