tan*_*lle 16 sql-server locking blocking
查询sys.dm_tran_locks
DMV 会向我们显示哪些会话 (SPID) 持有表、页和行等资源的锁。
对于获取的每个锁,是否有任何方法可以确定导致该锁的 SQL 语句(删除、插入、更新或选择)?
我知道,most_recent_query_handle
在列sys.dm_exec_connections
DMV给了我们执行的最后一个查询的文本,而是多次其他查询同一个会话(SPID)在跑前和仍持有锁。
我已经使用了这个sp_whoisactive
过程(来自 Adam Machanic),它只显示了此时输入缓冲区上的查询(想想DBCC INPUTBUFFER @spid
),这并不总是(在我的情况下通常永远不会)是获取锁的查询。
例如:
该sp_whoisactive
过程将指出第 3 步中的语句,该语句不是锁的负责人,因此没有用。
这个问题来自使用Blocked Process Reports功能进行分析,以找出生产中阻塞场景的根本原因。每个事务运行多个查询,并且大多数时候最后一个(显示在 BPR 的输入缓冲区中)很少是持有锁的那个。
我有一个后续问题:有效识别阻塞查询的框架
Han*_*non 16
SQL Server 不会保留已执行命令的历史记录1,2。您可以确定哪些对象有锁,但您不一定能看到是什么语句导致了这些锁。
例如,如果您执行此语句:
BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
Run Code Online (Sandbox Code Playgroud)
通过最近的 sql 句柄查看 SQL 文本,您会看到该语句确实出现了。但是,如果会话这样做:
BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO
Run Code Online (Sandbox Code Playgroud)
SELECT * FROM dbo.TestLock;
即使事务尚未提交,您也只会看到该语句,并且该INSERT
语句阻止了对该dbo.TestLock
表的读取。
我用它来寻找阻塞其他会话的未提交事务:
/*
This query shows sessions that are blocking other sessions, including sessions that are
not currently processing requests (for instance, they have an open, uncommitted transaction).
By: Max Vernon, 2017-03-20
*/
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; --reduce possible blocking by this query.
USE tempdb;
IF OBJECT_ID('tempdb..#dm_tran_session_transactions') IS NOT NULL
DROP TABLE #dm_tran_session_transactions;
SELECT *
INTO #dm_tran_session_transactions
FROM sys.dm_tran_session_transactions;
IF OBJECT_ID('tempdb..#dm_exec_connections') IS NOT NULL
DROP TABLE #dm_exec_connections;
SELECT *
INTO #dm_exec_connections
FROM sys.dm_exec_connections;
IF OBJECT_ID('tempdb..#dm_os_waiting_tasks') IS NOT NULL
DROP TABLE #dm_os_waiting_tasks;
SELECT *
INTO #dm_os_waiting_tasks
FROM sys.dm_os_waiting_tasks;
IF OBJECT_ID('tempdb..#dm_exec_sessions') IS NOT NULL
DROP TABLE #dm_exec_sessions;
SELECT *
INTO #dm_exec_sessions
FROM sys.dm_exec_sessions;
IF OBJECT_ID('tempdb..#dm_exec_requests') IS NOT NULL
DROP TABLE #dm_exec_requests;
SELECT *
INTO #dm_exec_requests
FROM sys.dm_exec_requests;
;WITH IsolationLevels AS
(
SELECT v.*
FROM (VALUES
(0, 'Unspecified')
, (1, 'Read Uncomitted')
, (2, 'Read Committed')
, (3, 'Repeatable')
, (4, 'Serializable')
, (5, 'Snapshot')
) v(Level, Description)
)
, trans AS
(
SELECT dtst.session_id
, blocking_sesion_id = 0
, Type = 'Transaction'
, QueryText = dest.text
FROM #dm_tran_session_transactions dtst
LEFT JOIN #dm_exec_connections dec ON dtst.session_id = dec.session_id
OUTER APPLY sys.dm_exec_sql_text(dec.most_recent_sql_handle) dest
)
, tasks AS
(
SELECT dowt.session_id
, dowt.blocking_session_id
, Type = 'Waiting Task'
, QueryText = dest.text
FROM #dm_os_waiting_tasks dowt
LEFT JOIN #dm_exec_connections dec ON dowt.session_id = dec.session_id
OUTER APPLY sys.dm_exec_sql_text(dec.most_recent_sql_handle) dest
WHERE dowt.blocking_session_id IS NOT NULL
)
, requests AS
(
SELECT des.session_id
, der.blocking_session_id
, Type = 'Session Request'
, QueryText = dest.text
FROM #dm_exec_sessions des
INNER JOIN #dm_exec_requests der ON des.session_id = der.session_id
OUTER APPLY sys.dm_exec_sql_text(der.sql_handle) dest
WHERE der.blocking_session_id IS NOT NULL
AND der.blocking_session_id > 0
)
, Agg AS (
SELECT SessionID = tr.session_id
, ItemType = tr.Type
, CountOfBlockedSessions = (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = tr.session_id)
, BlockedBySessionID = tr.blocking_sesion_id
, QueryText = tr.QueryText
FROM trans tr
WHERE EXISTS (
SELECT 1
FROM requests r
WHERE r.blocking_session_id = tr.session_id
)
UNION ALL
SELECT ta.session_id
, ta.Type
, CountOfBlockedSessions = (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = ta.session_id)
, BlockedBySessionID = ta.blocking_session_id
, ta.QueryText
FROM tasks ta
UNION ALL
SELECT rq.session_id
, rq.Type
, CountOfBlockedSessions = (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = rq.session_id)
, BlockedBySessionID = rq.blocking_session_id
, rq.QueryText
FROM requests rq
)
SELECT agg.SessionID
, ItemType = STUFF((SELECT ', ' + COALESCE(a.ItemType, '') FROM agg a WHERE a.SessionID = agg.SessionID ORDER BY a.ItemType FOR XML PATH ('')), 1, 2, '')
, agg.BlockedBySessionID
, agg.QueryText
, agg.CountOfBlockedSessions
, des.host_name
, des.login_name
, des.is_user_process
, des.program_name
, des.status
, TransactionIsolationLevel = il.Description
FROM agg
LEFT JOIN #dm_exec_sessions des ON agg.SessionID = des.session_id
LEFT JOIN IsolationLevels il ON des.transaction_isolation_level = il.Level
GROUP BY agg.SessionID
, agg.BlockedBySessionID
, agg.CountOfBlockedSessions
, agg.QueryText
, des.host_name
, des.login_name
, des.is_user_process
, des.program_name
, des.status
, il.Description
ORDER BY
agg.BlockedBySessionID
, agg.CountOfBlockedSessions
, agg.SessionID;
Run Code Online (Sandbox Code Playgroud)
如果我们在 SSMS 中设置一个带有几个查询窗口的简单测试平台,我们可以看到我们只能看到最近的活动语句。
在第一个查询窗口中,运行以下命令:
CREATE TABLE dbo.TestLock
(
id int NOT NULL IDENTITY(1,1)
);
BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
Run Code Online (Sandbox Code Playgroud)
在第二个窗口中,运行:
SELECT *
FROM dbo.TestLock
Run Code Online (Sandbox Code Playgroud)
现在,如果我们从上面运行未提交的阻塞事务查询,我们会看到以下输出:
?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ????????? ? 会话 ID ? 物品种类 ?BlockedBySessionID ? 查询文本? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ????????? ? 67 ? 交易 ?0 ? 开始交易? ? ? ? ? 插入 dbo.TestLock 默认值? ? 68 ? 会话请求,等待任务?67 ? 选择 * ? ? ? ? ? 从 dbo.TestLock ? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????
(我从结果的末尾删除了一些不相关的列)。
现在,如果我们将第一个查询窗口更改为:
BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO
Run Code Online (Sandbox Code Playgroud)
并重新运行第二个查询窗口:
SELECT *
FROM dbo.TestLock
Run Code Online (Sandbox Code Playgroud)
我们将看到阻塞事务查询的输出:
?????????????????????????????????????????????????????? ?????????????????????????????????????????? ? 会话 ID ? 物品种类 ?BlockedBySessionID ? 查询文本? ?????????????????????????????????????????????????????? ?????????????????????????????????????????? ? 67 ? 交易 ?0 ? 选择 * ? ? ? ? ? 从 dbo.TestLock; ? ? 68 ? 会话请求,等待任务?67 ? 选择 * ? ? ? ? ? 从 dbo.TestLock ? ?????????????????????????????????????????????????????? ??????????????????????????????????????????
1 - 不完全正确。有过程缓存,其中可能包含负责锁定的语句。但是,确定哪个语句是锁定的实际原因可能并不容易,因为缓存中可能有许多查询涉及相关资源。
下面的查询显示了上面测试查询的查询计划,因为我的过程缓存不是很忙。
SELECT TOP(30) t.text
, p.query_plan
, deqs.execution_count
, deqs.total_elapsed_time
, deqs.total_logical_reads
, deqs.total_logical_writes
, deqs.total_logical_writes
, deqs.total_rows
, deqs.total_worker_time
, deqs.*
FROM sys.dm_exec_query_stats deqs
OUTER APPLY sys.dm_exec_sql_text(deqs.sql_handle) t
OUTER APPLY sys.dm_exec_query_plan(deqs.plan_handle) p
WHERE t.text LIKE '%dbo.TestLock%' --change this to suit your needs
AND t.text NOT LIKE '/\/\/\/\/EXCLUDE ME/\/\/\/\/\'
ORDER BY
deqs.total_worker_time DESC;
Run Code Online (Sandbox Code Playgroud)
此查询的结果可能会让您找到罪魁祸首,但请注意,在繁忙的系统上,像这样检查过程缓存可能会要求很高。
2 SQL Server 2016 及更高版本提供Query Store,它确实保留了已执行查询的完整历史记录。
为了补充Max 的回答,我发现以下实用程序非常有用:
当我想深入研究阻塞并分析阻塞发生的原因和方式时,我会使用 beta_lockinfo - 这非常有用。
beta_lockinfo 是一个存储过程,它提供有关进程和它们持有的锁以及它们的活动事务的信息。beta_lockinfo 旨在收集尽可能多的有关阻塞情况的信息,以便您可以立即找到罪魁祸首并在情况紧急时终止阻塞进程。然后,您可以坐下来分析 beta_lockinfo 的输出,以了解阻塞情况是如何出现的,并找出要采取哪些措施来防止这种情况再次发生。beta_lockinfo 的输出显示了所有带有锁的活动进程和被动进程、它们锁定的对象、它们最后提交的命令以及它们正在执行的语句。您还可以获得当前语句的查询计划。