Як знайти запит, який все ще містить замок?


15

Запит sys.dm_tran_locksDMV показує, які сеанси (SPID) містять блокування на таких ресурсах, як таблиця, сторінка та рядок.

Чи існує спосіб придбання блокування для кожного придбаного блокування?

Я знаю, що most_recent_query_handleстовпець sys.dm_exec_connectionsDMV дає нам текст останнього виконаного запиту, але кілька разів інші запити виконувались раніше за той же сеанс (SPID) і все ще тримають блокування.

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

Наприклад:

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

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

Це питання з'явилося під час аналізу за допомогою функції Blocked Process Reports , щоб знайти першопричину блокування сценаріїв у виробництві. Кожна транзакція виконує кілька запитів, і більшість випадків останній (який відображається у вхідному буфері в BPR) рідко є тим, що містить блокування.

У мене є наступне питання: Рамка для ефективного визначення блокуючих запитів

Відповіді:


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

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

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗
║ SessionID ║ ItemType cked BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬══════════════════════════════════ ═══════╣
║ 67 ║ трансакція ║ 0 ║ ПОЧАТОК ТРАНЗАКЦІЯ ║
║ ║ ║ ║ ВСТАВИТЬСЯ в dbo.TestLock ЗАМЕЧАННІ ЦІННОСТІ ║
║ 68 ║ Запит на сеанс, завдання на очікування ║ 67 ║ ВИБІР * *
║ ║ ║ ║ ВІД dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩══════════════════════════════════ ═══════╝

(З кінця результатів я видалив кілька нерелевантних стовпців).

Тепер, якщо ми змінимо перше вікно запиту на це:

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

і повторно запустіть 2-е вікно запиту:

SELECT *
FROM  dbo.TestLock

Ми побачимо цей результат із запиту блокування транзакцій:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦════════════════════╗
║ SessionID ║ ItemType cked BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬════════════════════╣
║ 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;

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

2 SQL Server 2016 і вище пропозиція , від якого запит магазин , який робить зберегти повну історію виконаних запитів.


Дякую @Max, дуже добре пояснено. Цей сумнів народився під час аналізу Blocked Process Reportsособливостей, щоб знайти першопричину блокування сценаріїв у виробництві. Кожна транзакція виконує кілька запитів, і більшість випадків останній (який відображається у вхідному буфері в BPR) рідко є тим, що містить блокування. Здається, що мій останній ресурс для вирішення цього питання - встановити легкий сеанс xEvents, щоб повідомити мені, які запити виконувались під кожним сеансом. Якщо ви знаєте статтю, що показує приклад цього, я буду вдячний.
tanitelle

Що стосується магазину запитів, це дуже корисно, але не вистачає інформації про SPID. Все одно, дякую.
tanitelle


6

Щоб доповнити відповідь Макса , я знайшов нижче утиліти надзвичайно корисні:

  • beta_lockinfo - автор Ерланда Соммарського

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

beta_lockinfo - це збережена процедура, яка забезпечує інформацію про процеси та блокування, які вони проводять, а також про їх активні транзакції. beta_lockinfo призначений для того, щоб зібрати якомога більше інформації про блокуючу ситуацію, щоб миттєво знайти винуватця і вбити процес блокування, якщо ситуація відчайдушна. Потім ви можете сісти і проаналізувати вихід з beta_lockinfo, щоб зрозуміти, як виникла ситуація з блокуванням, і зрозуміти, які дії потрібно вжити, щоб запобігти повторному виникненню ситуації. Вихід з beta_lockinfo показує всі активні процеси, а також пасивні процеси з блокуваннями, які об'єкти вони блокуються, яку команду вони останнім чином подали та яку операцію вони виконують. Ви також отримуєте плани запитів для поточних висловлювань.


1
ого, що Ерланд Соммарського прос дивовижний.
Макс Вернон

1
Так, я використовую це, коли мені доведеться глибоко зануритися в блокуючі деталі.
Кін Шах
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.