必须在BEFORE DELETE触发器中用ROLLBACK(SQL Server)或SIGNAL/RAISE EXCEPTION(MySQL/PostgreSQL)显式终止事务,仅抛错不回滚无法阻止数据删除,且校验逻辑不可放在AFTER触发器中。

触发器里用 ROLLBACK 而不是 RETURN 或 RAISERROR 单独抛错
SQL Server 触发器中,仅靠 RAISERROR 抛异常不会自动回滚事务,数据仍可能被删掉。必须显式执行 ROLLBACK 才能真正拦截删除操作。
-
RAISERROR('禁止删除', 16, 1)只报错,不阻止提交;若外层事务未捕获,DELETE仍会成功 - 正确做法是先判断条件,再
ROLLBACK+RAISERROR组合使用 - MySQL 的
BEFORE DELETE触发器不能用ROLLBACK(不支持事务控制语句),得靠SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '禁止删除' - PostgreSQL 同样用
RAISE EXCEPTION,它默认终止当前事务,等效于自动回滚
校验逻辑写在 BEFORE DELETE 触发器里,别用 AFTER
AFTER DELETE 触发器运行时,行已经删了——这时候再想拦、再想恢复,只能靠查日志或备份,不现实。
- 所有拦截类校验必须放在
BEFORE DELETE中,这是唯一能“掐住脖子”的时机 - 注意
DELETED临时表在BEFORE触发器中不可用(SQL Server);要用IF EXISTS (SELECT 1 FROM deleted WHERE ...)判断,但实际执行时deleted表已存在(SQL Server 2005+ 支持) - MySQL 和 PostgreSQL 的
BEFORE触发器可直接访问即将被删的行(OLD),适合做字段级检查,比如IF OLD.status = 'active' THEN SIGNAL ... END IF
避免在触发器里查大表或调用函数,否则拖慢主操作
触发器是同步执行的,一旦里面做了耗时查询,DELETE 就会卡住,还可能引发死锁。
- 别在触发器里
JOIN几百万行的订单表去验证用户权限 - 优先用
EXISTS替代SELECT COUNT(*),用覆盖索引字段做判断 - 如果必须查外部状态,考虑把关键标识缓存在原表(如加
is_deletable BIT DEFAULT 1字段),触发器只查本行该字段 - 测试时用
SET STATISTICS IO ON(SQL Server)或EXPLAIN ANALYZE(PostgreSQL)看触发器内部是否扫全表
MySQL 8.0+ 的 SIGNAL 必须配合 DECLARE CONTINUE HANDLER?不,不用
很多人误以为 MySQL 触发器里抛 SIGNAL 前得先声明 handler,其实完全不需要。只要在 BEFORE 触发器里 SIGNAL,就会立即中断当前语句并报错。
- 错误写法:
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN SIGNAL ... END—— 多余且干扰逻辑 - 正确写法:直接
IF OLD.created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '7天内数据不可删除'; END IF; - 注意
SIGNAL在 MySQL 5.5–5.7 不可用,低版本只能靠写入错误日志或更新标记字段“软拦截”,效果有限
最易被忽略的是事务边界——比如应用层开了事务但没设 SET XACT_ABORT ON(SQL Server),触发器里 ROLLBACK 可能只回滚到保存点,而非整个事务。这类细节不测就容易漏掉。










