0%

SQL Server 查谁堵住了当前查询

语句突然不动,CPU 也不高,先看是不是被别的会话堵住了。堵住别人的那个会话叫 blocker,被堵住的是 waiter。要结束的是最上面那个 blocker,而不是每一个正在等待的会话。CPU 已经打满时,先看 正在跑的语句,这篇只处理阻塞。

在要查的实例上执行。blocking_session_id 不是 0,表示这一行正在等另一个会话。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SELECT
r.session_id,
r.blocking_session_id,
r.wait_type,
r.wait_time,
r.status,
DB_NAME(r.database_id) AS db_name,
SUBSTRING(
t.text,
(r.statement_start_offset / 2) + 1,
(
(
CASE r.statement_end_offset
WHEN -1 THEN DATALENGTH(t.text)
ELSE r.statement_end_offset
END - r.statement_start_offset
) / 2
) + 1
) AS running_statement
FROM sys.dm_exec_requests AS r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE r.session_id <> @@SPID
AND (
r.blocking_session_id <> 0
OR r.session_id IN (
SELECT blocking_session_id
FROM sys.dm_exec_requests
WHERE blocking_session_id <> 0
)
);

wait_type 常见是 LCK_M_S、LCK_M_X 这类锁等待。wait_time 单位是毫秒。running_statement 是当前这条语句,不是整个批处理。

最上面的 blocker 往往没有出现在 sys.dm_exec_requests 里,因为它已经不在跑请求,只是还拿着锁。用会话号去会话视图看是谁:

1
2
3
4
5
6
7
8
9
10
SELECT
session_id,
login_name,
host_name,
program_name,
status,
last_request_start_time,
open_transaction_count
FROM sys.dm_exec_sessions
WHERE session_id = 54; -- 换成 blocking_session_id

open_transaction_count 大于 0,说明事务还没提交或回滚。再看它最后执行的语句:

1
DBCC INPUTBUFFER(54);

应用连接池里,program_name 经常是同一串,要靠 host_name 和 last_request_start_time 对到那一次操作。

确认这是堵住别人的会话、并且事务可以放弃之后,才结束它:

1
KILL 54;

KILL 会回滚这个会话未提交的事务。事务很大时,回滚期间锁还在,等待不会马上消失。不要对一串 waiter 逐个 KILL。waiter 的 blocking_session_id 指向谁,就先处理谁;那个人如果也被别人堵住,继续往上找,直到 blocking_session_id 为 0 或者这个会话已经不在 dm_exec_requests 里。