必须同时关闭数据库级RECURSIVE_TRIGGERS和服务器级nested triggers才有效;触发器内更新同表必致递归溢出,需用INSTEAD OF触发器、业务层处理或TRIGGER_NESTLEVEL()守卫规避。
必须同时关闭数据库级 RECURSIVE_TRIGGERS 和服务器级 nested triggers,只关一个无效;触发器里更新同表是高危操作,不是“可能”溢出,而是只要逻辑闭环就必现。
SQL Server 有两个独立控制点,缺一不可:RECURSIVE_TRIGGERS 是数据库属性(默认 OFF),nested triggers 是实例级配置(默认 1,即启用)。只关前者,后者仍为 1 时,INSERT → 触发器 → UPDATE same_table → 另一个 AFTER UPDATE 触发器 依然会压栈。
立即验证:
SELECT DATABASEPROPERTYEX(DB_NAME(), 'IsRecursiveTriggersEnabled') —— 返回 1 表示已开启EXEC sp_configure 'nested triggers' —— 第二列值为 1 表示启用不能只靠 ALTER DATABASE 或只改 sp_configure,必须双管齐下:
ALTER DATABASE [your_db] SET RECURSIVE_TRIGGERS OFFEXEC sp_configure 'nested triggers', 0; RECONFIGURE注意:nested triggers 是实例级,修改后影响所有数据库;RECURSIVE_TRIGGERS 是库级,切换数据库后需重新确认。部署脚本里硬编码 SET RECURSIVE_TRIGGERS ON 是常见遗忘点。
在触发器中对当前表执行 UPDATE 或 INSERT 是最直接的爆栈路径,应优先规避:
INSTEAD OF 触发器接管原始操作,把逻辑收口,避免“提交后再触发”带来的隐式链IF TRIGGER_NESTLEVEL() > 1 RETURN,但这只是兜底,不是设计别依赖 TRIGGER_NESTLEVEL() 判断“是否自己调自己”——它只数嵌套层数,不管是不是同一触发器,也不跨线程感知并发递归。
它不报明确错误,而是连接突然中断、SSMS 卡死、日志里出现 StackOverflowError 或 Event loop exception,和 DBeaver 解析复杂 SQL 时的崩溃现象高度相似,容易误判为客户端问题。
上线前务必用边界值测试:插入/更新一行,立刻查 SELECT TRIGGER_NESTLEVEL(),若返回 ≥ 2 就说明已进入递归链。真正的难点不在“怎么关”,而在于识别那些看似无害的间接更新——比如 A 表触发器改 B 表,B 表触发器又反向改 A 表,环状依赖比自调用更难发现。