不能通过触发器捕获死锁事件,因为SQL Server死锁检测在内核层完成,跳过T-SQL执行路径,不触发TRIGGER、TRY...CATCH或任何用户代码;xml_deadlock_report扩展事件是唯一可靠捕获方式。

不能通过触发器捕获死锁事件——SQL Server 的死锁检测发生在引擎内核层,不经过任何 T-SQL 执行路径,TRIGGER 根本收不到信号。
为什么触发器里捕获不到 ERROR_NUMBER() = 1205
死锁不是一条可执行的语句,而是多个事务在持有锁的同时互相等待形成的瞬时闭环。SQL Server 检测到后会立即选一个牺牲品(blocking_session_id = -2),回滚其事务并抛出错误 1205。这个过程跳过所有用户代码,包括:
-
AFTER或INSTEAD OF触发器的执行入口 -
TRY...CATCH块(哪怕你写了,也进不去CATCH) - 触发器内部的任何日志写入逻辑(比如
INSERT INTO deadlock_log)
更危险的是:在触发器里加日志表写入,反而可能引入新锁,让死锁更容易发生。
扩展事件才是唯一可靠方式:xml_deadlock_report
从 SQL Server 2008 R2 起,xml_deadlock_report 是唯一能完整、实时、无侵入捕获死锁现场的机制。它自带毫秒级 timestamp、参与进程堆栈、资源列表,还能看到触发器是否卷入(只要它真在执行路径上)。
创建会话的关键点:
- 目标必须用
event_file(别用ring_buffer,内存溢出会丢历史) - 启用
collect_database_name = ON和collect_system_time = ON,否则上下文丢失 - 不要加
WHERE sqlserver.database_name = 'YourDB'过滤——死锁常跨库,一过滤就漏
示例语句:
CREATE EVENT SESSION [capture_deadlocks] ON SERVER ADD EVENT sqlserver.xml_deadlock_report ADD TARGET package0.event_file(SET filename=N'C:\XEvents\deadlocks.xel')
如何确认触发器是否参与了死锁
拿到 .xel 文件后,用 SSMS 打开或查 sys.fn_xe_file_target_read_file,重点看 XML 中两个位置:
-
<executionStack>下是否有procname="YourDB.dbo.trg_after_update_orders"——说明该触发器确实执行了,并持有了锁 -
<resource-list>中锁对象是否包含触发器访问的表(比如主语句改orders,触发器又去更新inventory,两表都会出现) - 比对
owner-list和waiter-list的顺序:若事务 A 先锁orders再等inventory,B 反过来,就构成环——而触发器让这个顺序变得隐蔽
如果非要“重试”,必须放在触发器外面
触发器内部做不了重试,但调用它的外部批处理可以。典型结构是用 WHILE + TRY...CATCH 包裹原始 DML:
- 只在外部捕获
ERROR_NUMBER() = 1205,失败后WAITFOR DELAY '00:00:00.05'再试 -
@maxRetry建议设为 3,再高容易引发重试风暴 - 触发器本身要轻量:避免跨库查询、远程调用、复杂循环,否则本身就是死锁温床
真正需要警惕的,是那些在触发器里偷偷改了别的表、又没加索引、还用了 SELECT ... FOR UPDATE 的逻辑——它们不会报错,但会在死锁报告的 <executionStack> 里留下清晰痕迹。

















