应优先查 sys.dm_exec_requests 中 blocking_session_id > 0 的会话,因其标识持锁未被阻塞的源头;该字段仅在 requests 视图中存在,需联查 sessions 和 sql_text 定位语句,配合 blocked process threshold + XEvent 实现自动捕获与安全 KILL。

查 blocking_session_id > 0 的会话,不是看 login_time
很多人一上来就查 sys.dm_exec_sessions 里 login_time 超过 30 分钟的会话,然后 KILL——这大概率误杀。真正该盯的是阻塞链顶端:谁在持锁不放、且没被别人阻塞?关键字段是 blocking_session_id。
必须联查 sys.dm_exec_requests,因为只有它暴露实时阻塞关系。单独查 sys.dm_exec_sessions 拿不到 blocking_session_id 值(该字段只在 requests 视图中存在)。
-
blocking_session_id > 0:该会话正在阻塞别人,且自身未被阻塞 → 优先 kill 目标 -
blocking_session_id = -2:已被 SQL Server 选为死锁牺牲品,但尚未终止 → 必须 kill,否则可能卡住资源释放 -
status = 'sleeping'且last_request_end_time很旧:仅说明空闲,不等于有害;得结合sys.dm_tran_locks确认是否还持有锁
用 sp_who2 或自定义查询定位阻塞源头和 SQL 文本
sp_who2 active 能快速看到 BlkBy 列,但它不返回 SQL 语句。真要定位到哪句存储过程里的 UPDATE 卡住了,得手动关联 sys.dm_exec_sql_text。
以下查询能同时拿到阻塞者、被阻塞者、各自执行的 SQL 片段、等待时长、客户端 IP:
SELECT
r.session_id,
r.blocking_session_id,
r.total_elapsed_time / 1000.0 AS [wait_sec],
s.host_name,
s.program_name,
s.client_net_address,
t.text AS [sql_text],
SUBSTRING(t.text, r.statement_start_offset/2 + 1,
(CASE WHEN r.statement_end_offset = -1
THEN LEN(CONVERT(NVARCHAR(MAX), t.text)) * 2
ELSE r.statement_end_offset END - r.statement_start_offset) / 2 + 1) AS [current_statement]
FROM sys.dm_exec_requests r
JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.blocking_session_id <> 0
AND r.session_id > 50
AND r.session_id <> @@SPID;注意:statement_start_offset 和 statement_end_offset 是字节偏移,除以 2 才对应 Unicode 字符位置;text 可能为空(比如用了加密存储过程或权限不足),需提前校验。
设置 blocked process threshold + Extended Events 捕获长期阻塞
靠人工轮询 DMV 是下策。SQL Server 提供了轻量级机制:blocked process threshold (s) 配置项,配合 system_health 默认 session 或自定义 XEvent 捕获阻塞事件。
启用方式(需 sysadmin 权限):
EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'blocked process threshold (s)', 5; -- 阻塞超 5 秒即触发报告 RECONFIGURE;
之后,阻塞达到阈值时,SQL Server 会生成 blocked_process_report 事件,包含完整 XML 报告,含 <blocking-process></blocking-process> 和 <blocked-process></blocked-process> 两块结构,可直接解析出阻塞者 SPID、SQL、等待资源类型(如 KEY、OBJECT)、甚至事务开始时间。
- 别用 SQL Trace 或 Profiler 长期跑,开销大、易拖慢实例
- 默认
system_health已捕获该事件,但只保留最近 1 小时左右;如需归档,建独立 XEvent session 并写入文件目标 - XML 中
waittime单位是毫秒,resource_description如KEY:13:72057595528151040(f2008a1296d8)表示具体索引键,可用于反查表和索引
KILL 之前必须验证 is_user_process 和当前无活跃请求
直接 KILL @spid 风险极高。系统会话(如 LAZY WRITER、CHECKPOINT)若被误杀,可能导致 checkpoint 失败或缓存刷盘异常。
安全 kill 的三步检查:
- 查
sys.dm_exec_sessions.is_user_process = 1,排除系统会话 - 确认该 SPID 在
sys.dm_exec_requests中不存在活跃记录(即没正在执行语句),或虽存在但blocking_session_id = -2 - 用参数化方式执行 KILL:
EXEC sys.sp_executesql N'KILL @p1', N'@p1 int', @p1 = @spid,避免字符串拼接注入和类型转换失败
务必加 TRY...CATCH,捕获 Msg 6103(会话已断开)、Msg 1205(死锁牺牲品已结束)等非致命错误,防止存储过程因单个 KILL 失败而中断后续逻辑。
最常被忽略的一点:阻塞未必来自存储过程本身,可能是它调用的另一个未提交事务、或嵌套的动态 SQL 持有锁后异常退出未回滚。所以 kill 后还得查 sys.dm_tran_active_transactions 和 sys.dm_tran_session_transactions,确认锁是否真正释放。

















