死锁图(xml_deadlock_report)是唯一能直接确认触发器是否参与死锁的证据,需在<executionStack>中查找procname含trg_前缀的<frame>,如trg_after_update_order;若未显式命名,则结合sys.dm_tran_locks中异常锁组合(如主SQL为UPDATE orders却持有audit_log锁)交叉验证。

怎么看死锁图里有没有触发器参与
死锁图(xml_deadlock_report)是唯一能直接确认触发器是否卷入死锁的证据。它不靠猜,靠 executionStack 里的调用栈。
打开死锁 XML,定位 <executionStack> 节点,逐个看每个 <frame> 的 procname 属性:如果出现类似 trg_after_insert_order、dbo.trg_update_log 这样的名字,就坐实了触发器执行路径;如果只有主 SQL(如 INSERT INTO orders),但对应 process 的 inputbuf 又没其他 DML,就得怀疑触发器被隐式调用但未显式命名——这时要结合锁表交叉验证。
常见误判点:
-
sys.dm_exec_trigger_stats里的execution_count上升 ≠ 当前死锁由它引起,只是说明它最近跑得多 - 触发器内调用存储过程时,
procname可能只显示外层触发器名,实际锁在内部语句上,需用sql_handle反查完整文本 - XML 中
stmtsimple的queryplan可能省略触发器逻辑,别只信图形化死锁图,必须手动展开 XML 源码
怎么从锁视图反推触发器在锁哪张表
sys.dm_tran_locks 不标触发器,但能暴露“不该出现在这里”的锁。关键不是看谁持锁,而是看锁的组合是否违背业务逻辑。
例如主 SQL 是 UPDATE orders,你却在 sys.dm_tran_locks 里发现同一 session_id 同时持有 orders 和 audit_log 的 X 锁,而 audit_log 表只在 trg_after_update_orders 里被 INSERT —— 这就是强信号。
实操建议:
- 用
SELECT request_session_id, resource_type, resource_description, request_mode FROM sys.dm_tran_locks WHERE resource_database_id = DB_ID()先捞出当前所有锁,过滤request_status = 'GRANT'(已持锁)和'WAIT'(等待锁) - 对每个阻塞会话(
blocking_session_id > 0),用sys.dm_exec_requests查它的sql_handle,再通过sys.dm_exec_sql_text取原始语句——如果语句里没写UPDATE audit_log,但锁里有,基本就是触发器干的 - 注意
resource_associated_entity_id对应的是对象 ID,OBJECT_NAME()在跨库场景下可能返回NULL,要用sys.objects或sys.tables显式 JOIN 确认表名
为什么不能靠 sys.dm_os_waiting_tasks 定位触发器
这个视图告诉你“谁在等什么资源”,但不告诉你“谁加的锁”。触发器没有独立 session_id,它的锁全部挂在父会话下,导致你看到一个会话在等 LCK_M_U,却无法区分是主 SQL 还是触发器里的 UPDATE 加的。
更麻烦的是:wait_resource 字段只给对象 ID + 索引 ID,比如 5:123456789:1,你得自己去查这到底对应哪张表、哪个索引——而触发器常操作日志表或配置表,这些表在主 SQL 的 WHERE 条件里根本不会出现,容易被忽略。
所以它只能辅助,不能定案。真正有用的组合是:
-
sys.dm_exec_requests找阻塞链 -
sys.dm_tran_locks看锁分布 -
sys.dm_exec_sql_text反查语句内容 - 三者交叉比对,才能把触发器“从幕后拽出来”
触发器内事务边界失控的典型表现
SQL Server 触发器没有独立事务,它和主语句共用同一个 @@TRANCOUNT。这意味着触发器里任何 DML 都会立刻加锁,且直到主事务 COMMIT 或 ROLLBACK 才释放——哪怕触发器只执行了一行 UPDATE users,也会把 users 表某行锁住几十秒。
容易踩的坑:
- 触发器里调
EXEC xp_cmdshell或sp_OACreate做 HTTP 请求,网络延迟直接拉长锁持有时间 - 写日志时用
INSERT INTO t_audit但t_audit缺少索引,导致插入变慢,锁住更多行 - 嵌套调用含 DML 的存储过程,且该过程没设
SET XACT_ABORT ON,异常后事务状态残留,锁不释放 - 误用
IF @@TRANCOUNT = 0 BEGIN BEGIN TRAN ...—— 在触发器里永远为 false,但开发者以为“安全”就放行了复杂逻辑
最隐蔽的问题是:单次触发器执行耗时突增(比如从 1ms 跳到 500ms),往往不是代码问题,而是它访问的某张表正在被重建索引、或统计信息严重过期,导致执行计划退化——这种性能抖动和死锁高发时段高度重合,但日志里只报 1205,不报慢查询。

















