游客发表
凌晨两点,手机开始疯狂弹告警。锁语订单库的询死写入全堵住了,几十个会话卡在 WAITFOR 状态,锁语应用层报了一堆超时。询死登录服务器第一件事——不是锁语重启,是询死查死锁。SQL Server 不会主动把死锁的锁语元凶交到你手上,得自己动手翻。询死
下面这些语句,锁语我用了快八年。询死每次线上出事,锁语先跑一遍,询死基本能定位到是锁语哪两条 SQL 在互相掐脖子。
打开 SSMS,询死连上实例,直接执行:
SELECT session_id, blocking_session_id, wait_type, wait_time, wait_resource, last_wait_type, transaction_id FROM sys.dm_exec_requests WHERE blocking_session_id <> 0; 结果里出现 blocking_session_id 非零的行,就是被堵住的倒霉蛋。但注意,这只能看到阻塞,不一定已经形成死锁循环。SQL Server 的死锁检测器每隔几秒跑一次,真死锁了会自动选一个牺牲品回滚,报错 1205。所以有时候你查的时候,死锁已经结束了,只剩下阻塞链。
想抓现行,得靠扩展事件或者 trace flag。不过日常排查,下面这个语句更直接——它把当前所有会话的锁和等待全列出来:
SELECT t1.resource_type, t1.resource_database_id, t1.resource_associated_entity_id, t1.request_mode, t1.request_session_id, t2.blocking_session_id, t2.wait_type, t2.wait_time, t2.last_wait_type FROM sys.dm_tran_locks t1 INNER JOIN sys.dm_exec_requests t2 ON t1.request_session_id = t2.session_id WHERE t2.blocking_session_id > 0 ORDER BY t2.wait_time DESC; 这个查询能告诉你:谁在等什么资源,谁拿着锁不放,等了多久。wait_time 越大,说明堵得越久。如果看到两个会话互相等待对方的资源,那就是死锁的雏形。
WHERE t1.request_session_id > 50,把系统会话过滤掉。生产库上系统会话的锁信息会干扰判断。 阻塞查询只能看到“现在”,死锁往往发生在几秒前。要事后追查,开一个扩展事件会话,盯着 xml_deadlock_report 事件。图形界面里建太慢,直接 T-SQL 上:
CREATE EVENT SESSION [capture_deadlock] ON SERVER ADD EVENT sqlserver.xml_deadlock_report ADD TARGET package0.ring_buffer WITH (MAX_MEMORY=4096 KB, EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS); GO ALTER EVENT SESSION [capture_deadlock] ON SERVER STATE = START; GO 跑起来之后,一旦发生死锁,环形缓冲区里就有 XML 报告。查的时候用这个:
SELECT XEvent.query('(event/data/value/deadlock)[1]') AS DeadlockGraph FROM ( SELECT CAST(target_data AS XML) AS TargetData FROM sys.dm_xe_session_targets st JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address WHERE s.name = 'capture_deadlock' AND st.target_name = 'ring_buffer' ) AS Data CROSS APPLY TargetData.nodes('RingBufferTarget/event') AS XEventData(XEvent); 出来的 DeadlockGraph 是一大坨 XML。别慌,点开看 <process-list> 和 <resource-list>。每个 process 里有 spid、inputbuf(就是那条 SQL)、waitresource。resource-list 里能看到锁的类型和对象。两下一对照,谁锁了谁、谁等了谁,清清楚楚。
我见过最离谱的一次,两个存储过程互相调,一个先更新 A 表再查 B 表,另一个反过来。并发一上来,死锁率 30%。XML 报告里 inputbuf 直接显示 exec sp_update_order 和 exec sp_check_stock,连参数都带出来了。
SQL Server 默认有个 system_health 扩展事件会话,里面已经包含了死锁报告。直接查:
SELECT XEvent.query('(event/data/value/deadlock)[1]') AS DeadlockGraph, XEvent.value('(event/@timestamp)[1]', 'datetime2') AS EventTime FROM ( SELECT CAST(target_data AS XML) AS TargetData FROM sys.dm_xe_session_targets st JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address WHERE s.name = 'system_health' AND st.target_name = 'ring_buffer' ) AS Data CROSS APPLY TargetData.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS XEventData(XEvent) ORDER BY EventTime DESC; 这个不用提前开,默认就在跑。缺点是环形缓冲区大小有限,如果死锁太频繁,旧报告会被冲掉。生产环境还是单独建一个会话更稳妥。
查死锁的时候,很多人只看 sys.dm_exec_requests,忘了看 sys.dm_os_waiting_tasks。后者更底层,能拿到等待资源的详细描述。比如:
SELECT waiting_task_address, session_id, wait_duration_ms, wait_type, resource_description FROM sys.dm_os_waiting_tasks WHERE blocking_session_id IS NOT NULL; resource_description 字段会告诉你具体是哪个页、哪个键、哪个对象。配合 sys.dm_tran_locks 一起看,基本不会漏。
最后说个实在话:死锁没法完全避免,只能减少。查出来之后,要么改索引,要么调事务隔离级别,要么把大事务拆小。光靠查语句,治标不治本。但至少,下次凌晨告警的时候,你能五分钟内把锅甩给那条烂 SQL,而不是干瞪眼。
```友情链接