长事务阻塞必须主动干预,不能等待结束;应通过sys.dm_exec_requests与sys.dm_tran_locks联查,筛选open_transaction_count>0且status='sleeping'的会话,结合last_request_end_time、wait_type和sql_handle精准识别“睡死”事务并决定KILL或修复。

长事务阻塞问题不能靠等它自己结束,必须立刻定位持有者、判断是否已“睡死”,再决定是 KILL 还是修复应用逻辑。
查谁在长时间占着锁不放
用 sys.dm_exec_requests 和 sys.dm_tran_locks 联查,重点筛出 open_transaction_count > 0 且 status = 'sleeping' 的会话——这类就是典型的“事务没提交就挂了”:
- 执行时间超过 30 秒、
wait_type为空(wait_type = 0x0000)、open_tran > 0:大概率是应用超时后没回滚,连接还留着未提交事务 - 执行时间短但
blocking_session_id > 0:说明它正在被别人锁住,得顺藤摸瓜查阻塞源头 -
last_request_end_time很旧,但open_tran = 1:基本可判定是遗留事务,不是活跃操作
快速定位语句:
SELECT
r.session_id,
r.status,
r.command,
r.wait_type,
r.open_transaction_count,
s.login_name,
s.host_name,
s.program_name,
t.text AS last_sql
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.open_transaction_count > 0 AND s.status = 'sleeping';区分是真忙还是假死
不能一看到 open_tran > 0 就 KILL,得先看它到底在干啥:
- 如果
status = 'running'且wait_type是WRITELOG或ASYNC_IO_COMPLETION:说明事务确实在写日志或刷盘,属于真忙,需结合磁盘 I/O 看是否硬件瓶颈 - 如果
status = 'sleeping'且last_request_end_time是几小时前的:基本是应用异常退出后没清理事务,可以安全 KILL - 如果
status = 'runnable'但wait_type是LCK_M_U或LCK_M_X:说明它正卡在抢锁,要查它等的是哪行、谁在持锁
注意:sp_who2 显示的 blk 列只反映当前瞬时阻塞关系,不够准;优先用 sys.dm_os_waiting_tasks 查实时等待链。
为什么 SET XACT_ABORT ON 必须加在存储过程开头
很多长事务阻塞,根源是存储过程里某条语句失败后,后续的 ROLLBACK 根本没执行到。加 SET XACT_ABORT ON 是最简单有效的兜底:
- 它让任何运行时错误(包括客户端取消、超时、约束冲突)都自动触发事务回滚,不会留下
open_tran = 1的 sleeping 会话 - 必须放在
BEGIN TRY外面,否则 TRY/CATCH 捕获错误后,XACT_ABORT不生效 - 和
IF @@TRANCOUNT > 0 ROLLBACK不冲突,但后者依赖代码路径全覆盖,容易漏;前者是引擎级保障
示例写法:
CREATE PROCEDURE usp_update_order_status
AS
BEGIN
SET XACT_ABORT ON; -- 必须第一句
BEGIN TRY
BEGIN TRANSACTION;
UPDATE TOP (5000) orders SET status = 'shipped' WHERE ...;
COMMIT;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH
END;别信“等一会儿就好”,KILL 前先确认三件事
KILL 一个会话不是终点,而是排查闭环的起点。动手前必须确认:
- 该会话对应的
host_name和program_name是否属于你负责的应用?避免误杀监控、ETL 或备份任务 - 它的
sql_handle对应的语句是否含UPDATE/DELETE?如果是纯SELECT,可能只是客户端没取完结果集(类型3阻塞),KILL 反而会让 SQL Server 花30秒强制收尾 - 它有没有在做
ROLLBACK(status = 'rollback')?这时 KILL 会延长释放时间,不如让它自己完成
真正危险的是那种 sleeping + open_tran = 1 + last_request_end_time 几小时没动的会话——这种不处理,它就会一直锁着数据页,其他所有想改同一批订单的请求全被堵死。

















