触发器中禁止用return终止删除,须抛异常(raise/throw/signal);避免复杂查询与聚合,优先用外键约束;truncate不触发触发器,批量操作需谨慎。

DELETE 触发器里不能用 RETURN 终止操作
SQL Server 和 PostgreSQL 的 BEFORE DELETE 触发器里,想“拦住”非法删除,不能靠 RETURN 或 RETURN NULL —— 这在 PostgreSQL 里会直接报错,在 SQL Server 里压根不生效。真正有效的做法是抛出异常:RAISE EXCEPTION(PostgreSQL)或 THROW(SQL Server)。MySQL 的 BEFORE DELETE 触发器则根本不允许执行 ROLLBACK,只能靠 SIGNAL 抛错。
- PostgreSQL 示例:
RAISE EXCEPTION '订单状态为已完成,禁止删除'; - SQL Server 示例:
THROW 50000, '该用户仍有未结清账单,不可删除', 1; - MySQL 示例:
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '关联设备未解绑,禁止删除';
触发器里查关联数据必须加事务隔离注意点
业务规则常需检查外键引用、状态字段或时间窗口,比如“删除用户前确认无未完成订单”。但触发器运行在当前 DELETE 事务中,若用 SELECT 查其他表,可能读到未提交的脏数据,或被其他并发事务干扰。尤其在 READ COMMITTED 隔离级别下,两次查询之间数据可能已变。
- 避免在触发器里做复杂 JOIN 或子查询,容易锁表或拖慢主操作
- 如需强一致性校验,PostgreSQL 可用
SELECT ... FOR SHARE锁住关联行;SQL Server 建议加WITH (UPDLOCK, HOLDLOCK) - MySQL 不支持行级共享锁语法,得靠应用层重试 + 应用侧加锁兜底
触发器无法替代应用层校验,尤其涉及多表状态聚合
比如“删除部门前检查该部门下所有员工薪资总和是否为零”,这种跨多行、需 SUM 聚合的逻辑,放在触发器里不仅性能差,还容易因触发时机(如批量 DELETE)导致结果不准。更糟的是,某些数据库(如 MySQL 5.7)在触发器中调用函数或访问其他表时,会隐式禁用部分优化,甚至报 Can't update table in stored function/trigger 错误。
- 聚合类、跨多表 JOIN 类、调用外部服务类规则,一律放应用层做
- 触发器只做轻量、确定性、单行/单表级判断:如
status != 'draft'、deleted_at IS NULL、created_at > NOW() - INTERVAL '30 days' - 别在触发器里写
COUNT(*)或SUM()—— 它们不是原子的,也不可缓存
MySQL 8.0+ 的 FOREIGN KEY 级联限制比触发器更可靠
如果业务规则本质是“有子记录就不能删父记录”,优先用外键约束而非触发器。MySQL 8.0 支持 ON DELETE RESTRICT(默认),且它在引擎层校验,不依赖 SQL 解析,性能高、无竞态、不绕过。而自定义触发器在 LOAD DATA、mysqldump --skip-triggers 或禁用触发器时完全失效。
- 外键能拦截
DELETE、UPDATE、TRUNCATE(后者触发器根本收不到) - 触发器对
TRUNCATE TABLE无效 —— 这是 DDL,不走 DML 触发器链 - 上线前务必验证:
SHOW CREATE TABLE看外键是否真建成功,有些 ORM 会静默跳过
实际用的时候,最常被忽略的是触发器与批量操作的兼容性——一次 DELETE FROM users WHERE team_id = 123 可能触发几百次校验,每条都去查订单表,瞬间打满连接池。这时候得看清楚:规则到底是不是真的必须在数据库层硬卡。










