最可靠解法是在触发器开头加运行时标记;sql server用context_info设会话级标记并校验,mysql设max_sp_recursion_depth=1,postgresql用pg_trigger_depth()>1判断嵌套深度。

直接在触发器开头加运行时标记,是目前最可靠、副作用最小的解法;靠 UPDATE() 或 RECURSIVE_TRIGGERS 配置根本拦不住自引用循环。
SQL Server:用 CONTEXT_INFO 做会话级触发标记
SQL Server 允许触发器更新自己表,且默认静默递归——RECURSIVE_TRIGGERS OFF 对 A→A 自调用完全无效,@@NESTLEVEL 在并行或视图触发路径下不准,临时表跨会话不可见。
真正可控的方式是业务 SQL 执行前设标记,触发器读取后立即退出:
- 应用层或存储过程开头执行:
SET CONTEXT_INFO 0x54524947474552(即 "TRIGGER" 的 ASCII 十六进制) - 触发器第一行必须写:
IF CONTEXT_INFO() = 0x54524947474552 RETURN - 别用
CONVERT(VARCHAR, CONTEXT_INFO())做字符串比较——128 字节可能被截断或含空字节 - 若触发器由存储过程调用,过程结尾记得清空:
SET CONTEXT_INFO 0x0
MySQL:必须设 max_sp_recursion_depth = 1 会话级限制
MySQL 不是“循环后报错”,而是在解析阶段就禁止触发器内对本表做任何 DML,只要代码里出现 UPDATE t 就立刻抛 ERROR 1442。但跨表更新(比如 t1 触发器改 t2)如果 t2 也有触发器,就会形成链式递归,此时靠 max_sp_recursion_depth 截断才是关键。
- 该变量只能在会话级设置:
SET SESSION max_sp_recursion_depth = 1 - 不能在触发器体内写
SET,MySQL 语法不允许 - 连接池场景(如
pymysql)必须每次获取连接后重置,否则可能复用旧值 - 设为 1 表示“原始语句触发一次,内部再触发就拒绝”;设为 0(不限制)等于放弃防护
PostgreSQL:靠 pg_trigger_depth() + TG_OP 组合判断
PostgreSQL 默认允许递归,也不硬拦,但给你留了精准控制接口:pg_trigger_depth() 返回当前嵌套层数(首次为 1),TG_OP 告诉你当前是 INSERT、UPDATE 还是 DELETE。
- 触发器开头必须加:
IF pg_trigger_depth() > 1 THEN RETURN; END IF; - 更精细控制可结合:
IF TG_OP = 'UPDATE' AND pg_trigger_depth() > 1 THEN RETURN; - 不要只依赖
pg_trigger_depth() > 1就跳过所有逻辑——有些合法场景(如树形路径更新)需要深度 > 1 时仍执行部分操作 -
pg_trigger_depth()是函数调用,开销极小,比查临时表或中间表稳定得多
跨表互触时,列级判断(如 COLUMNS_UPDATED() 或 UPDATE(col))完全防不住循环——它们只管“这次 SET 有没有写某列”,不管这次更新是不是由另一个触发器发起的。真正要拦住的是“谁发起的”,不是“改了啥”。










