触发器执行超时表现为事务卡住、连接中断或timeout expired错误,而非直接报“超时”;根本原因是触发器延长加锁路径、导致死锁(1205)或隐式同步阻塞(如http调用、全表扫描、游标循环、大日志写入)。

触发器执行超时不会直接报“超时”错误,而是表现为事务卡住、客户端连接中断或 Lock wait timeout exceeded 类似现象——但 SQL Server 实际上不抛这个错,它更常见的是连接重置、语句无响应、或报错 Timeout expired(来自客户端驱动),而数据库端日志里往往只有模糊的 1205 deadlock 或静默挂起。
查死锁图里有没有触发器执行栈
SQL Server 的死锁不是“触发器导致”的,而是触发器让加锁路径变长、顺序不可控。一旦出现 1205,立刻抓取死锁 XML:
- 在 SQL Server Management Studio 中打开“活动监视器 → 死锁图”,右键保存为 XML
- 搜索
<executionstack></executionstack>下是否有类似trg_after_insert_order或proc_log_audit这样的对象名 - 重点看
inputbuf里是不是主语句(如UPDATE orders SET status = 'shipped'),再顺藤摸瓜确认 executionStack 是否调用了触发器内逻辑 - 如果 executionStack 显示的是
EXECUTE sp_executesql或嵌套存储过程,需用sys.dm_exec_sql_text反查 handle 对应的真实语句,避免被包装层掩盖
用 sys.dm_exec_requests 看触发器是否真在跑
当 UPDATE/INSERT 响应变慢,别只盯着执行计划——触发器可能正在后台阻塞:
- 运行
SELECT session_id, blocking_session_id, wait_type, wait_time, last_wait_type, command, sql_text.text FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) sql_text WHERE r.status = 'running' AND r.command IN ('UPDATE', 'INSERT', 'DELETE') - 关注
wait_type:如果是ASYNC_NETWORK_IO,说明结果集发不出去,大概率是触发器里做了大日志写入或远程调用;如果是LCK_M_U或PAGEIOLATCH_SH,说明触发器正扫表或等锁 -
blocking_session_id非零且指向另一个running会话?查那个会话的sql_text,大概率也是同表 DML + 触发器,形成锁链
临时禁用触发器验证是否是根因
比读代码快得多的验证方式:直接关掉它,重放操作,看超时是否消失。
- 禁用:
DISABLE TRIGGER trg_name ON table_name - 验证后务必恢复:
ENABLE TRIGGER trg_name ON table_name - 注意:
DISABLE TRIGGER ALL会同时禁用约束触发器(比如外键级联),测试完必须逐个检查并启用,否则后续 INSERT 可能违反约束却不报错 - 禁用期间补测关键路径:比如审计日志是否断档、下游 ETL 是否收不到变更、状态机是否卡住——触发器失效的影响常比性能问题更隐蔽
检查触发器里有没有隐式同步阻塞操作
很多“超时”根本不是数据库慢,而是触发器把不该同步做的事硬塞进事务:
-
sp_OACreate/sp_OAMethod调 HTTP 接口:网络延迟直接卡死整个事务,且无法设超时 - 未加
NOLOCK的跨表查询(如SELECT COUNT(*) FROM audit_log WHERE ref_id = i.id):若audit_log表大又没索引,每行都触发全表扫描+共享锁 - 循环处理
inserted表(DECLARE cur CURSOR FOR SELECT * FROM inserted):N 行 = N 次独立 DML,锁时间线性增长 - 写大体积日志(如
INSERT INTO huge_audit_table):日志文件 IO 打满,其他会话一起卡住
真正危险的不是触发器本身,而是它把本该异步、可丢弃、带重试的逻辑,绑在了强一致、必须成功、不能中断的事务里。哪怕只是发一条钉钉通知,也足以让订单提交接口在大促时批量超时——而且你很难从 sys.dm_exec_sessions 里一眼看出那条“钉钉通知”正在拖垮整个库存服务。











