sql server没有“死锁触发器”,死锁由引擎内核瞬时检测并终止牺牲品事务,不经过t-sql路径,触发器无法捕获1205错误;唯一实时无侵入方式是扩展事件xml_deadlock_report,可精准定位触发器在executionstack和resource-list中的参与痕迹。

不能用触发器监控死锁发生时刻——SQL Server 没有“死锁触发器”这种东西,强行在触发器里埋点只会漏掉、延迟甚至干扰死锁判定。
为什么触发器不适合记录死锁时刻
死锁是两个及以上事务互相持有并等待对方资源的瞬时状态,SQL Server 在检测到死锁后会立即选择一个牺牲品(blocking_session_id = -2),回滚其事务并抛出错误 1205。这个过程发生在引擎内核层,**不经过任何用户定义的 T-SQL 执行路径**,所以:
- 触发器只响应 DML(
INSERT/UPDATE/DELETE)语句,而死锁本身不是语句,是锁等待关系的闭环结果 - 你无法在触发器里捕获
ERROR_NUMBER() = 1205,因为错误发生在触发器执行完成之后、事务提交/回滚阶段 - 若在触发器里写日志表(如
INSERT INTO deadlock_log),反而可能引入新锁,加剧死锁概率
真正该用的机制:扩展事件(Extended Events)
从 SQL Server 2008 R2 起,xml_deadlock_report 事件是唯一能**完整、实时、无侵入**捕获死锁现场的方式。它自带时间戳(timestamp 字段精确到毫秒)、参与进程、资源类型、执行堆栈(含触发器名,如果它参与了):
- 创建会话时必须启用
collect_database_name = ON和collect_system_time = ON,否则丢失上下文 - 不要用
WHERE sqlserver.database_name = 'YourDB'过滤——死锁可能跨库,过滤会丢事件 - 目标选
event_file(非ring_buffer),避免内存溢出丢失历史 - 示例启动语句:
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 打开或用 T-SQL 查询(sys.fn_xe_file_target_read_file),重点看 XML 中两个位置:
-
<executionstack></executionstack>下是否有类似procname="YourDB.dbo.trg_after_update_inventory"的节点——说明该触发器实际执行并持有了锁 -
<resource-list></resource-list>中锁对象是否包含触发器内访问的表(比如主语句改orders,触发器又去更新inventory,那两表都会出现在这里) - 注意
owner-list和waiter-list的顺序:若一个事务先锁orders再等inventory,另一个反过来,就构成典型死锁环——触发器让这个顺序变得隐蔽
如果非要“关联触发器行为”,只能靠间接方式
你无法让触发器“记录死锁”,但可以监控它是否**高频、长时间运行**,从而预判死锁风险:
- 查
sys.dm_exec_trigger_stats,重点关注last_execution_time和execution_count突增——结合sys.dm_exec_requests中对应 DML 的total_elapsed_time > 500000(0.5 秒),说明触发器可能拖慢了宿主语句 - 对高危触发器加轻量日志:在开头插一行
INSERT INTO trigger_trace (trigger_name, spid, start_ts) VALUES ('trg_u_orders', @@SPID, GETDATE(2)),结尾再插一行带end_ts,用差值估算耗时(别用NOW(6),SQL Server 不支持) - 禁用所有触发器的
PRINT或RAISERROR(..., 0, 1) WITH NOWAIT,它们会强制刷缓冲区,放大锁持有时间
真正关键的不是“记录死锁时刻”,而是从 xml_deadlock_report 里还原出谁、在哪、按什么顺序加了哪些锁——触发器只是执行链上的一环,它的名字藏在 XML 深处,而不是日志表某一行里。










