SQL Server触发器防递归最可靠方案是CONTEXT_INFO标记法:业务SQL前设SET CONTEXT_INFO 0x54524947474552,触发器开头检查并RETURN;避免用@@NESTLEVEL、UPDATE()或RECURSIVE_TRIGGERS开关,因其无法区分合法嵌套与自调用死循环。

SQL Server触发器里用CONTEXT_INFO防递归最可靠
别信@@NESTLEVEL,它数不清“合法嵌套”和“自调用死循环”的区别,尤其在视图触发、多层存储过程调用路径下容易误判。真正能隔离会话、轻量、无需建表的方案是CONTEXT_INFO。
- 业务SQL执行前设标记:
SET CONTEXT_INFO 0x54524947474552(即"TRIGGER"的ASCII十六进制) - 触发器开头立即检查:
IF CONTEXT_INFO() = 0x54524947474552 RETURN - 避免用
CONVERT(VARCHAR, CONTEXT_INFO())比较——可能因编码或截断失效 - 若触发器被存储过程调用,过程开头设标记、结尾清空:
SET CONTEXT_INFO 0x0
跨表互触场景下UPDATE()函数根本防不住循环
UPDATE(col)只检测SET子句是否显式写了该列名,不关心值是否真变,更不管这次更新是谁发起的。t1触发器改t2,t2触发器再改t1,UPDATE()在两边都返回TRUE,循环照常成立。
- 真正能识别“哪几列被改”的是
COLUMNS_UPDATED(),它返回位图,配合位运算判断第N列(索引从0开始):IF (COLUMNS_UPDATED() & POWER(2, 1)) = POWER(2, 1) - 字段顺序影响位图位置,增删列后必须同步调整位运算逻辑
- 禁用对方触发器(
ALTER TABLE ... DISABLE TRIGGER)是常见做法,但必须严格配对启用,否则残留禁用状态会导致数据不同步
别碰RECURSIVE_TRIGGERS这个开关
RECURSIVE_TRIGGERS OFF是数据库级开关,关了它会影响所有触发器,包括那些本就需要自引用的合法场景(比如树形结构的路径更新)。它对“自引用触发器”完全无效——SQL Server的递归控制粒度不在数据库配置层,而在运行时上下文。
- 这个选项只控制“同一触发器是否允许被自己再次触发”,不解决跨表、跨库、跨过程引发的间接循环
- 用
TRIGGER_NESTLEVEL(OBJECT_ID(N'dbo.YourTriggerName')) > 1看似精准,但实际中触发器名可能动态生成或重命名,维护成本高 - 临时表或表变量存标记不可靠:临时表跨会话不可见,
@@SPID在并行执行下可能重复
MySQL和PostgreSQL用户注意机制完全不同
SQL Server这套CONTEXT_INFO方案不能直接套用。MySQL在解析阶段就硬拦截自更新,报ERROR 1442;PostgreSQL默认放行但必须靠pg_trigger_depth()手动截断,且它只返回当前嵌套层数,不区分触发来源。
- MySQL:AFTER里任何对本表的DML都会被拦,BEFORE里只能用
SET NEW.col = ...修改待插入值 - PostgreSQL:必须写
IF pg_trigger_depth() > 1 THEN RETURN; END IF;,但要注意如果触发器调用了含DML的函数,仍可能绕过该判断 - 跨库操作不等于安全——
UPDATE other_db.users若目标表也有触发器,照样形成反向递归
UPDATE()或@@NESTLEVEL就以为万事大吉——它们在真实复杂链路中几乎必然失效。

















