SQL Server中触发器递归导致死锁的典型表现是语句长时间卡住、sp_who2显示WAITFOR或LCK_M_U锁等待,或报错“Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32)”;可通过ALTER DATABASE [YourDB] SET RECURSIVE_TRIGGERS OFF禁用数据库级递归,或在触发器内用TRIGGER_NESTLEVEL()>1 RETURN防护。

SQL Server 中触发器递归导致死锁的典型表现
当你在 AFTER INSERT 触发器里更新同一张表,又恰好该更新再次触发自身,就可能进入无限递归 —— 表现为语句长时间卡住、sp_who2 显示 WAITFOR 或 LCK_M_U 等锁等待,甚至直接报错 Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32)。
如何关闭触发器的递归调用(SERVER 级开关)
SQL Server 默认允许间接递归(即通过其他对象链式触发),但可通过禁用数据库级递归开关来阻断。这不是在触发器里写逻辑控制,而是配置层面的硬性限制:
- 执行
ALTER DATABASE [YourDB] SET RECURSIVE_TRIGGERS OFF—— 这会禁止所有AFTER触发器的间接递归(但INSTEAD OF不受影响) - 该设置对新连接立即生效,已有连接需重连才生效
- 注意:它影响整个数据库的所有表,不是单个触发器可配;若业务依赖跨表触发链(如订单插入 → 更新库存 → 记录日志),关掉后链会中断
触发器内部如何避免自我触发(推荐做法)
比全局关递归更可控的方式,是在触发器开头加防护逻辑,尤其适用于必须保留递归能力但要防死循环的场景:
- 检查
TRIGGER_NESTLEVEL():在触发器第一行加IF TRIGGER_NESTLEVEL(OBJECT_ID(N'dbo.YourTriggerName')) > 1 RETURN - 用临时表或表变量缓存“正在处理中”状态,配合
IF EXISTS(SELECT 1 FROM #InTrigger) RETURN - 避免在
AFTER触发器中做UPDATE同一张表;改用INSTEAD OF+ 显式INSERT/UPDATE,或把副作用逻辑拆到存储过程中异步处理
MySQL / PostgreSQL 用户注意:机制完全不同
MySQL 没有内置触发器递归开关,max_sp_recursion_depth 只控存储过程,对触发器无效;实际靠开发者手动加标志位(如新增 is_processing 字段或使用 GET_LOCK())。
PostgreSQL 的触发器默认不递归,但若触发器内执行 UPDATE 引起相同事件,仍会再次触发 —— 必须用 pg_trigger_depth() 判断嵌套深度,或用 tg_op + 会话级变量(SET LOCAL)标记。
不同数据库的“递归”定义和控制粒度差异很大,别直接套用 SQL Server 的 RECURSIVE_TRIGGERS 思路。

















