Как найти запрос, который все еще держит блокировку?

15

Запрос sys.dm_tran_locksDMV показывает нам, какие сеансы (SPID) удерживают блокировки таких ресурсов, как таблица, страница и строка.

Для каждой полученной блокировки есть ли способ определить, какой оператор SQL (удалить, вставить, обновить или выбрать) вызвал эту блокировку?

Я знаю, что most_recent_query_handleстолбецsys.dm_exec_connections DMV дает нам текст последнего выполненного запроса, но несколько раз другие запросы выполнялись ранее в рамках одного сеанса (SPID) и все еще удерживают блокировки.

Я уже использую sp_whoisactiveпроцедуру (от Адама Мачаника), и она показывает только запрос, который находится на входном буфере в данный момент (подумайте DBCC INPUTBUFFER @spid), который не всегда (и в моем случае обычно никогда) не является запросом, который получил блокировку.

Например:

  1. открытая транзакция / сессия
  2. выполнить оператор (который удерживает блокировку ресурса)
  3. выполнить другое заявление в том же сеансе
  4. откройте другую транзакцию / сеанс и попытайтесь изменить ресурс, заблокированный на шаге 2.

sp_whoisactiveПроцедура будет указывать на утверждение на шаге 3, который не является ответственным за замком, и , следовательно , не полезно.

Этот вопрос возник из анализа с использованием функции отчетов о заблокированных процессах , чтобы найти основную причину блокирования сценариев в производстве. Каждая транзакция выполняет несколько запросов, и большую часть времени последний (который отображается во входном буфере в BPR) редко является тем, который удерживает блокировку.

У меня есть дополнительный вопрос: структура для эффективной идентификации блокирующих запросов

tanitelle
источник

Ответы:

15

SQL Server не хранит историю команд, которые были выполнены 1,2 . Вы можете определить , какие объекты имеют замки, но вы не можете обязательно видеть , что заявление вызвало эти замки.

Например, если вы выполните эту инструкцию:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

И посмотрите на текст SQL через самый последний дескриптор sql, вы увидите, что этот оператор действительно появляется. Однако, если сессия сделала это:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO

Вы увидите только 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;

Если мы настроим простой тестовый стенд в SSMS с парой окон запросов, мы увидим, что видим только последнее активное утверждение.

В первом окне запроса выполните это:

CREATE TABLE dbo.TestLock
(
    id int NOT NULL IDENTITY(1,1)
);
BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

Во втором окне запустите это:

SELECT *
FROM  dbo.TestLock

Теперь, если мы запустим запрос непринятых блокирующих транзакций сверху, мы увидим следующий вывод:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗
ID SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬══════════════════════════════════ ═══════╣
║ 67 ║ Сделка ║ 0 ║ НАЧАЛО СДЕЛКИ ║
║ ║ ║ ║ ВСТАВЬТЕ В dbo.TestLock ЗНАЧЕНИЯ ПО УМОЛЧАНИЮ ║
║ 68 Request Запрос сеанса, задача ожидания ║ 67 ║ SELECT * ║
D ║ ║ ║ ОТ dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩══════════════════════════════════ ═══════╝

(Я удалил некоторые несоответствующие столбцы в конце результатов).

Теперь, если мы изменим первое окно запроса на это:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO

и повторно запустите второе окно запроса:

SELECT *
FROM  dbo.TestLock

Мы увидим этот вывод из запроса блокирующих транзакций:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦════════════════════╗
ID SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬════════════════════╣
║ 67 ║ Транзакция ║ 0 ║ ВЫБРАТЬ * ║
D ║ ║ ║ ОТ dbo.TestLock; ║
║ 68 Request Запрос сеанса, задача ожидания ║ 67 ║ SELECT * ║
D ║ ║ ║ ОТ 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;

Результаты этого запроса могут позволить вам найти виновника, но имейте в виду, что проверка кэша процедур может быть довольно сложной в загруженной системе.

2 SQL Server 2016 и выше предложение , от которого запрос магазин , который делает сохранить полную историю выполненных запросов.

Макс Вернон
источник
Спасибо @Max, очень хорошо объяснил. Это сомнение возникло при анализе Blocked Process Reportsвозможностей, чтобы найти основную причину блокирования сценариев в производстве. Каждая транзакция выполняет несколько запросов, и большую часть времени последний (который отображается во входном буфере в BPR) редко является тем, который удерживает блокировку. Похоже, что мой последний ресурс для решения этой проблемы - установить облегченный сеанс xEvents, чтобы сообщить мне, какие запросы выполнялись при каждом сеансе. Если вы знаете статью, показывающую пример этого, я буду благодарен.
Tanitelle
Что касается магазина запросов, он очень полезен, но в нем отсутствует информация о SPID. Спасибо, в любом случае.
Tanitelle
в значительной степени дубликат dba.stackexchange.com/questions/187794/…
Митч Уит
6

Чтобы дополнить ответ Макса , я нашел ниже очень полезные утилиты:

Я использую beta_lockinfo, когда хочу глубоко погрузиться в блокировку и проанализировать, что и как возникло блокирование, что чрезвычайно полезно.

beta_lockinfo - это хранимая процедура, которая предоставляет информацию о процессах и блокировках, которые они удерживают, а также их активные транзакции. beta_lockinfo предназначена для сбора как можно большего количества информации о ситуации с блокировкой, чтобы вы могли мгновенно найти виновного и убить процесс блокировки, если ситуация окажется в отчаянном положении. Затем вы можете откинуться на спинку кресла и проанализировать выходные данные из beta_lockinfo, чтобы понять, как возникла ситуация с блокировкой, и выяснить, какие действия следует предпринять, чтобы предотвратить повторение ситуации. Выходные данные из beta_lockinfo показывают все активные процессы, а также пассивные процессы с блокировками, какие объекты они блокируют, какую команду они отправили в последний раз и какую инструкцию они выполняют. Вы также получаете планы запросов для текущих операторов.

Кин Шах
источник
1
вау, что Erland Sommarskog proc удивителен
Макс Вернон,
1
Да, я использую его, когда мне нужно глубоко погрузиться в детали блокировки.
Кин Шах