Wie finde ich die Abfrage, die noch eine Sperre enthält?

15

Das Abfragen der sys.dm_tran_locksDMV zeigt uns, welche Sitzungen (SPIDs) Ressourcen wie Tabelle, Seite und Zeile sperren.

Gibt es eine Möglichkeit, für jede erworbene Sperre zu bestimmen, welche SQL-Anweisung (Löschen, Einfügen, Aktualisieren oder Auswählen) diese Sperre verursacht hat?

Ich weiß, dass die most_recent_query_handleSpalte der sys.dm_exec_connectionsDMV den Text der zuletzt ausgeführten Abfrage enthält, aber mehrere Male andere Abfragen zuvor unter derselben Sitzung (SPID) ausgeführt wurden und noch Sperren aufweisen.

Ich verwende bereits die sp_whoisactiveProzedur (von Adam Machanic) und sie zeigt nur die Abfrage, die sich im Moment im Eingabepuffer befindet (think DBCC INPUTBUFFER @spid), die nicht immer (und in meinem Fall normalerweise nie) die Abfrage ist, die die Sperre erhalten hat.

Beispielsweise:

  1. offene Transaktion / Sitzung
  2. eine Anweisung ausführen (die eine Sperre für eine Ressource enthält)
  3. Führen Sie eine weitere Anweisung in derselben Sitzung aus
  4. Öffnen Sie eine andere Transaktion / Sitzung und versuchen Sie, die in Schritt 2 gesperrte Ressource zu ändern.

Die sp_whoisactiveProzedur weist auf die Anweisung in Schritt 3 hin, die nicht für die Sperre verantwortlich und daher nicht nützlich ist.

Diese Frage ergab sich aus einer Analyse mit der Funktion " Blockierte Prozessberichte" , um die Hauptursache für Blockierungsszenarien in der Produktion zu ermitteln. Jede Transaktion führt mehrere Abfragen aus. In den meisten Fällen ist die letzte Abfrage (die im Eingabepuffer von BPR angezeigt wird) selten die gesperrte.

Ich habe eine Folgefrage: Framework zur effektiven Identifizierung blockierender Abfragen

Tanitelle
quelle

Antworten:

15

SQL Server speichert keinen Verlauf der ausgeführten Befehle 1,2 . Sie können bestimmen, welche Objekte Sperren haben, aber Sie können nicht unbedingt sehen, welche Anweisung diese Sperren verursacht hat.

Wenn Sie beispielsweise diese Anweisung ausführen:

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

Wenn Sie sich den SQL-Text über das neueste SQL-Handle ansehen, sehen Sie, dass die Anweisung angezeigt wird. Wenn die Sitzung dies jedoch tat:

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

Sie sehen nur die SELECT * FROM dbo.TestLock;Anweisung, obwohl die Transaktion noch nicht festgeschrieben wurde, und die INSERTAnweisung blockiert Leser für die dbo.TestLockTabelle.

Ich verwende dies, um nach nicht festgeschriebenen Transaktionen zu suchen, die andere Sitzungen blockieren:

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

Wenn wir in SSMS eine einfache Testumgebung mit einigen Abfragefenstern einrichten, sehen wir, dass nur die zuletzt aktive Anweisung angezeigt wird.

Führen Sie im ersten Abfragefenster Folgendes aus:

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

Führen Sie im zweiten Fenster Folgendes aus:

SELECT *
FROM  dbo.TestLock

Wenn wir nun die obige Abfrage für nicht festgeschriebene Blockierungstransaktionen ausführen, sehen wir die folgende Ausgabe:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗
║ SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬══════════════════════════════════ ═══════╣
║ 67 ║ Transaktion ║ 0 ║ TRANSAKTION BEGINNEN ║
║ ║ ║ ║ INSERT IN dbo.TestLock DEFAULT VALUES ║
~ 68 ~ Sitzungsanforderung, wartende Aufgabe ~ 67 ~ AUSWÄHLEN * ~
║ ║ ║ ║ FROM dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩══════════════════════════════════ ═══════╝

(Ich habe einige irrelevante Spalten am Ende der Ergebnisse entfernt).

Wenn wir nun das erste Abfragefenster folgendermaßen ändern:

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

und führen Sie das 2. Abfragefenster erneut aus:

SELECT *
FROM  dbo.TestLock

Diese Ausgabe wird in der Abfrage zum Sperren von Transaktionen angezeigt:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦════════════════════╗
║ SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║
╠═══════════╬═══════════════════════════════╬═════ ═══════════════╬════════════════════╣
~ 67 ~ Transaktion ~ 0 ~ AUSWÄHLEN * ~
║ ║ ║ ║ FROM dbo.TestLock; ║
~ 68 ~ Sitzungsanforderung, wartende Aufgabe ~ 67 ~ AUSWÄHLEN * ~
║ ║ ║ ║ FROM dbo.TestLock ║
╚═══════════╩═══════════════════════════════╩═════ ═══════════════╩════════════════════╝

1 - nicht ganz richtig. Es gibt den Prozedur-Cache, der möglicherweise die Anweisung enthält, die für die Sperre verantwortlich ist. Es ist jedoch möglicherweise nicht einfach zu bestimmen, welche Anweisung die eigentliche Ursache für die Sperre ist, da sich möglicherweise viele Abfragen im Cache befinden, die die betreffende Ressource berühren.

Die folgende Abfrage zeigt den Abfrageplan für die obigen Testabfragen, da mein Prozedurcache nicht sehr ausgelastet ist.

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;

Mit den Ergebnissen dieser Abfrage können Sie möglicherweise den Täter finden. Beachten Sie jedoch, dass das Überprüfen des Prozedur-Cache auf einem ausgelasteten System sehr anstrengend sein kann.

2 SQL Server 2016 und höher bieten den Abfragespeicher , in dem der vollständige Verlauf der ausgeführten Abfragen gespeichert ist.

Max Vernon
quelle
Danke @Max, sehr gut erklärt. Dieser Zweifel wurde bei der Analyse von Blocked Process ReportsFeatures aufgeworfen, um die Hauptursache für Blockierungsszenarien in der Produktion zu finden. Jede Transaktion führt mehrere Abfragen aus. In den meisten Fällen ist die letzte Abfrage (die im Eingabepuffer von BPR angezeigt wird) selten die gesperrte. Es scheint, dass meine letzte Ressource zur Behebung dieses Problems darin besteht, eine einfache xEvents-Sitzung einzurichten, um mir mitzuteilen, welche Abfragen in jeder Sitzung ausgeführt wurden. Wenn Sie einen Artikel kennen, der ein Beispiel dafür zeigt, bin ich Ihnen dankbar.
Tanitelle
Auch in Bezug auf Query Store ist es sehr nützlich, aber es fehlen die SPID-Informationen. Danke trotzdem.
Tanitelle
ziemlich genau ein Duplikat von dba.stackexchange.com/questions/187794/…
Mitch Wheat
6

Um die Antwort von Max zu ergänzen , habe ich die folgenden Dienstprogramme als äußerst nützlich empfunden:

Ich benutze die beta_lockinfo, wenn ich tief in das Blockieren eintauchen und analysieren möchte, was und wie das Blockieren aufgetreten ist - was äußerst nützlich ist.

beta_lockinfo ist eine gespeicherte Prozedur, die Informationen zu Prozessen und deren Sperren sowie zu den aktiven Transaktionen bereitstellt. beta_lockinfo wurde entwickelt, um so viele Informationen wie möglich über eine Blockierungssituation zu sammeln, damit Sie den Täter sofort finden und den Blockierungsprozess beenden können, wenn die Situation verzweifelt ist. Dann können Sie sich zurücklehnen und die Ausgabe von beta_lockinfo analysieren, um zu verstehen, wie die Blockierungssituation entstanden ist, und herauszufinden, welche Maßnahmen ergriffen werden müssen, um ein erneutes Auftreten der Situation zu verhindern. Die Ausgabe von beta_lockinfo zeigt alle aktiven und passiven Prozesse mit Sperren an, welche Objekte sie sperren, welchen Befehl sie zuletzt übergeben haben und welche Anweisung sie ausführen. Sie erhalten auch die Abfragepläne für die aktuellen Anweisungen.

Kin Shah
quelle
1
Wow, dieser Erland Sommarskog-Prozess ist unglaublich.
Max Vernon
1
Yeh .. ich benutze es, wenn ich tief in blockierende Details eintauchen muss.
Kin Shah