触发器不能将逻辑删除转为物理删除,否则会引发递归、报错或破坏原子性;正确做法是禁用DELETE权限,通过专用存储过程(如sp_hard_delete_user)在确认软删后受控执行物理删除。

触发器不能把逻辑删除“转成”物理删除——它最多帮你识别出该删,然后由你主动执行 DELETE;真正在触发器里执行 DELETE 会引发递归、权限或语法错误,必须绕开。
BEFORE UPDATE 触发器里不能执行 DELETE
常见错误是想在用户把 is_deleted 设为 1 后,自动物理删掉那行:DELETE FROM users WHERE id = NEW.id。这行不通:
- MySQL 5.7+ 明确禁止在触发器中对同一表执行
DELETE(报错ERROR 1442) - 即使 MySQL 8.0 某些配置下允许,也会触发自身再次进入
BEFORE UPDATE或BEFORE DELETE,形成无限递归 - 触发器无事务上下文控制权,
DELETE成功后无法回滚主语句,破坏原子性
BEFORE DELETE 触发器里改 UPDATE 也救不了物理删除
有人试在 BEFORE DELETE 里写 UPDATE users SET is_deleted = 1 WHERE id = OLD.id,以为能“拦截”。结果是:
- 这条
UPDATE可能成功,但原DELETE仍继续执行(除非你SIGNAL中断) - 最终记录被物理删掉,软删字段根本没机会生效
- 若未加
WHERE id = OLD.id,可能误更新全表(OLD.id是唯一可靠引用)
真正可行的路径:用存储过程封装硬删逻辑
逻辑删除后,真要物理清理,得走显式、受控、带校验的通道,而不是靠触发器“自动转”:
- 建专用存储过程,比如
sp_hard_delete_user(IN p_id INT),里面先查SELECT deleted_at FROM users WHERE id = p_id AND is_deleted = 1,确认已软删再执行DELETE FROM users WHERE id = p_id - 给运维账号单独授权执行该过程,但不授
DELETE权限 - 过程里可加日志写入、外键检查、关联表清理(如
DELETE FROM orders WHERE user_id = p_id),这些在触发器里做风险太高 - 避免在触发器中调用该过程——嵌套触发器默认关闭,且难以调试
物理删除命令本身很简单,但时机和权限必须人工把控
最终执行物理删除只有一条语句,但它绝不能出现在触发器里:
DELETE FROM users WHERE is_deleted = 1 AND deleted_at
这个语句适合定时任务或 DBA 手动执行。容易被忽略的是:
- WHERE 条件漏判
is_deleted = 1就会误删活跃数据 - 没加时间范围(如
deleted_at老于 90 天)会导致刚软删的数据被立刻清掉 - 外键约束没设
ON DELETE CASCADE或手动清理,会因约束失败而中断

















