Jak znaleźć zapytanie, które nadal trzyma blokadę?

15

Zapytanie sys.dm_tran_locksDMV pokazuje nam, które sesje (SPID) blokują zasoby, takie jak tabela, strona i wiersz.

Czy dla każdej nabytej blokady jest jakiś sposób ustalenia, która instrukcja SQL (usuń, wstaw, aktualizuj lub wybierz) spowodowała tę blokadę?

Wiem, że most_recent_query_handlekolumnasys.dm_exec_connections DMV podaje nam tekst ostatnio wykonanego zapytania, ale kilka razy inne zapytania były uruchamiane wcześniej w ramach tej samej sesji (SPID) i nadal trzymają blokady.

Już korzystam z sp_whoisactiveprocedury (od Adama Machanica) i pokazuje ona tylko zapytanie znajdujące się obecnie w buforze wejściowym (pomyśl DBCC INPUTBUFFER @spid), co nie zawsze (aw moim przypadku zwykle nigdy) jest zapytaniem, które uzyskało blokadę.

Na przykład:

  1. otwarta transakcja / sesja
  2. wykonaj instrukcję (która blokuje zasób)
  3. wykonaj inną instrukcję w tej samej sesji
  4. otwórz kolejną transakcję / sesję i spróbuj zmodyfikować zasób zablokowany w kroku 2.

sp_whoisactiveProcedura będzie zwrócić uwagę na stwierdzenie w kroku 3, który nie jest odpowiedzialny za zamkiem, a tym samym nie użyteczne.

To pytanie pochodzi z analizy przeprowadzonej przy użyciu raportów zablokowanego procesu , aby znaleźć podstawową przyczynę blokowania scenariuszy w produkcji. Każda transakcja uruchamia kilka zapytań i przez większość czasu ostatnia (pokazywana w buforze wejściowym w BPR) rzadko ma blokadę.

Mam pytanie uzupełniające: Framework do efektywnej identyfikacji blokujących zapytań

tanitelle
źródło

Odpowiedzi:

15

SQL Server nie przechowuje historii poleceń, które zostały wykonane 1,2 . Możesz określić, które obiekty mają blokady, ale niekoniecznie możesz zobaczyć, które oświadczenie spowodowało te blokady.

Na przykład, jeśli wykonasz tę instrukcję:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

I spójrz na tekst SQL za pomocą najnowszego uchwytu sql, zobaczysz, że instrukcja się pojawia. Jeśli jednak sesja to zrobiła:

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

Zobaczysz tylko SELECT * FROM dbo.TestLock;wyciąg, nawet jeśli transakcja nie została zatwierdzona, a INSERTwyciąg blokuje czytelników przeciwko dbo.TestLocktabeli.

Używam tego do wyszukiwania niezaangażowanych transakcji, które blokują inne sesje:

/*
    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;

Jeśli skonfigurujemy proste łóżko testowe w SSMS z kilkoma oknami zapytań, możemy zobaczyć, że możemy zobaczyć tylko ostatnią aktywną instrukcję.

W pierwszym oknie zapytania uruchom to:

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

W drugim oknie uruchom to:

SELECT *
FROM  dbo.TestLock

Teraz, jeśli uruchomimy zapytanie niezaangażowanych transakcji blokujących z góry, zobaczymy następujące dane wyjściowe:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗
║ SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬══════════════════════════════════ ═══════╣
║ 67 ║ Transakcja ║ 0 ║ ROZPOCZNIJ TRANSAKCJĘ ║
║ ║ ║ ║ WSTAWIĆ DO dbo.TestLock WARTOŚCI DOMYŚLNE ║
║ 68 Request Żądanie sesji, zadanie oczekujące ║ 67 ║ WYBIERZ * ║
ROM ║ ║ ROM FROM dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩══════════════════════════════════ ═══════╝

(Usunąłem kilka niepotrzebnych kolumn na końcu wyników).

Teraz, jeśli zmienimy pierwsze okno zapytania na to:

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

i ponownie uruchom drugie okno zapytania:

SELECT *
FROM  dbo.TestLock

Zobaczymy dane wyjściowe z zapytania dotyczącego transakcji blokujących:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦════════════════════╗
║ SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬════════════════════╣
║ 67 ║ Transakcja ║ 0 ║ WYBIERZ * ║
║ ║ ║ ║ FROM dbo.TestLock; ║
║ 68 Request Żądanie sesji, zadanie oczekujące ║ 67 ║ WYBIERZ * ║
ROM ║ ║ ROM FROM dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩════════════════════╝

1 - nie do końca prawda. Istnieje pamięć podręczna procedur, która może zawierać instrukcję odpowiedzialną za blokadę. Jednak ustalenie, która instrukcja jest faktyczną przyczyną blokady, może nie być łatwe, ponieważ w pamięci podręcznej może znajdować się wiele zapytań dotykających dany zasób.

Poniższe zapytanie pokazuje plan zapytań dla powyższych zapytań testowych, ponieważ moja pamięć podręczna procedur nie jest bardzo zajęta.

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;

Wyniki tego zapytania mogą pozwolić ci znaleźć winowajcę, ale pamiętaj, że sprawdzanie pamięci podręcznej procedur w ten sposób może być dość wymagające w zajętym systemie.

2 SQL Server 2016 i powyżej OFERTA Store zapytań , które nie zachowują pełną historię zapytań wykonywanych.

Max Vernon
źródło
Dzięki @Max, bardzo dobrze wyjaśnione. Ta wątpliwość narodziła się podczas analizy Blocked Process Reportsfunkcji, aby znaleźć podstawową przyczynę blokowania scenariuszy w produkcji. Każda transakcja uruchamia kilka zapytań i przez większość czasu ostatnia (pokazywana w buforze wejściowym w BPR) rzadko ma blokadę. Wydaje się, że moim ostatnim zasobem do rozwiązania tego problemu jest ustawienie lekkiej sesji xEvent, aby powiedzieć mi, jakie zapytania były wykonywane w ramach każdej sesji. Jeśli znasz artykuł z tego przykładem, będę wdzięczny.
tanitelle
Również w przypadku Query Store jest bardzo przydatny, ale brakuje mu informacji SPID. W każdym razie dzięki.
tanitelle
prawie duplikat dba.stackexchange.com/questions/187794/...
Mitch Wheat
6

Aby uzupełnić odpowiedź Maxa , znalazłem poniższe narzędzia niezwykle przydatne:

Korzystam z beta_lockinfo, gdy chcę zagłębić się w blokowanie i analizować, co i jak powstało blokowanie - co jest niezwykle przydatne.

beta_lockinfo to procedura składowana, która dostarcza informacji o procesach i blokadach, które przechowują, a także o ich aktywnych transakcjach. beta_lockinfo ma na celu zebranie jak największej ilości informacji o sytuacji blokowania, abyś mógł natychmiast znaleźć winowajcę i zabić proces blokowania, jeśli sytuacja jest desperacka. Następnie możesz usiąść i przeanalizować dane wyjściowe z beta_lockinfo, aby zrozumieć, jak powstała sytuacja blokowania i dowiedzieć się, jakie działania należy podjąć, aby zapobiec ponownemu wystąpieniu sytuacji. Dane wyjściowe z beta_lockinfo pokazuje wszystkie aktywne procesy, a także procesy pasywne z blokadami, które obiekty blokują, jakie polecenie ostatnio przesłały i jakie polecenie wykonują. Otrzymasz również plany zapytań dla bieżących instrukcji.

Kin Shah
źródło
1
wow, że Erland Sommarskog proc jest niesamowity.
Max Vernon
1
Yeh .. używam go, kiedy muszę zagłębić się w blokowanie szczegółów.
Kin Shah