真正要修的是触发器和业务逻辑之间的耦合关系;sql server需用trigger_nestlevel()短路递归,mysql禁止触发器改自身表,postgresql依赖pg_trigger_depth()控制层级。

直接调大 max_sp_recursion_depth 或打开 RECURSIVE_TRIGGERS 不能修复问题,只会掩盖设计缺陷——真正要修的是触发器和业务逻辑之间的耦合关系。
SQL Server:别信 RECURSIVE_TRIGGERS,用 TRIGGER_NESTLEVEL() 短路
数据库级开关 RECURSIVE_TRIGGERS 对“自己改自己”类递归完全无效,它只管跨表间接触发。真正起作用的是运行时判断:
- 在触发器第一行写
IF TRIGGER_NESTLEVEL(OBJECT_ID(N'dbo.trg_YourTable_Update')) > 1 RETURN;—— 必须传对象 ID,字符串名会返回 0 - 避免用
@@NESTLEVEL,它混入了存储过程、函数等层数,不可靠 - 如果触发器里调用了存储过程,而该过程又更新了同一张表,
TRIGGER_NESTLEVEL()仍能正确返回 ≥2,这点很关键 - 配合
CONTEXT_INFO做会话标记更稳妥:设SET CONTEXT_INFO 0x54524947474552("TRIGGER"),开头检查匹配就退出
MySQL:ERROR 1442 不是深度问题,是硬性禁止
ERROR 1442 和递归深度无关,是 MySQL 内核的执行栈保护机制——你根本不能在触发器里 UPDATE、INSERT 或 DELETE 自己所属的表。此时调 max_sp_recursion_depth 完全没用。
- 绕过方法只有三种:把变更逻辑提到应用层;用通知表+异步作业;或加业务字段做标记(如
is_processing TINYINT DEFAULT 0) - 标记字段必须配事务清理:触发器开头查
IF NEW.is_processing = 1 THEN LEAVE proc_label;,结尾UPDATE SET is_processing = 0 WHERE id = NEW.id; -
max_sp_recursion_depth只影响存储过程调用,对触发器本身无约束;设为 0 表示禁用所有递归,设为 1 才是安全边界
PostgreSQL:pg_trigger_depth() 是唯一靠谱的运行时依据
PG 默认允许触发器递归,但不会自动阻止无限循环。没有全局开关,只能靠函数实时识别嵌套层级。
- 在触发器函数开头加
IF pg_trigger_depth() > 1 THEN RETURN; END IF; - 更精细控制可结合
TG_OP:比如只对AFTER UPDATE做深度拦截,INSERT仍放行 - 别用
current_setting('app.in_trigger', true)配合set_config()——会话变量在事务回滚后可能残留,且并发下易冲突 -
pg_backend_pid()不可用:同一事务内多次触发 PID 不变,无法区分层级
最常被忽略的一点:所谓“递归”,90% 源于隐式触发——比如触发器调了一个存储过程,过程里又 UPDATE 了原表;或者用了 OPENQUERY 更新本地表。这些路径不会出现在触发器代码里,但 TRIGGER_NESTLEVEL() 和 pg_trigger_depth() 都能捕获。修之前,先用 sp_who2 或 pg_stat_activity 看清到底卡在哪一层。











