it-swarm.com.de

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

Abfrage des sys.dm_tran_locks DMV zeigt uns, welche Sitzungen (SPIDs) Sperren für Ressourcen wie Tabelle, Seite und Zeile enthalten.

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

Ich weiß, dass die most_recent_query_handle Spalte der sys.dm_exec_connections DMV gibt uns den Text der zuletzt ausgeführten Abfrage an, aber mehrere andere Abfragen wurden zuvor unter derselben Sitzung (SPID) ausgeführt und halten immer noch Sperren.

Ich benutze bereits die sp_whoisactive Prozedur (von Adam Machanic) und zeigt nur die Abfrage an, die sich gerade im Eingabepuffer befindet (denken Sie an DBCC INPUTBUFFER @spid), was nicht immer (und in meinem Fall normalerweise nie) die Abfrage ist, die die Sperre erhalten hat.

Zum Beispiel:

  1. transaktion/Sitzung öffnen
  2. eine Anweisung ausführen (die eine Ressource sperrt)
  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.

Das sp_whoisactive Die Prozedur 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 Blocked Process Reports , um die Hauptursache für Blockierungsszenarien in der Produktion zu ermitteln. Jede Transaktion führt mehrere Abfragen aus, und meistens ist die letzte (die im Eingabepuffer bei BPR angezeigt wird) selten diejenige, die die Sperre hält.

Ich habe eine Folgefrage: Framework zur effektiven Identifizierung blockierender Abfragen

15
tanitelle

SQL Server speichert keinen Verlauf der ausgeführten Befehle1,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, werden Sie feststellen, dass diese 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 Anweisung SELECT * FROM dbo.TestLock;, Obwohl die Transaktion noch nicht festgeschrieben wurde und die Anweisung INSERT die Leser für die Tabelle dbo.TestLock Blockiert.

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 einen einfachen Prüfstand in SSMS mit einigen Abfragefenstern einrichten, können wir sehen, 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 Abfrage für nicht festgeschriebene blockierende Transaktionen von oben ausführen, wird die folgende Ausgabe angezeigt:

╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗ 
 ║ SessionID ║ ItemType ║ BlockedBySessionID ║ QueryText ║ 
 ╠═══════════╬══════════ ═════════════════════╬════════════════════╬═══════ ══════════════════════════════════╣ 
 ║ 67 ║ Transaktion ║ 0 ║ TRANSAKTION BEGINNEN ║ 
 ║ ║ ║ ║ EINFÜGEN TO dbo.TestLock STANDARDWERTE ║ 
 ║ 68 ║ Sitzungsanforderung, Warteaufgabe ║ 67 ║ SELECT * ║ 
 ║ ║ d FROM dbo.TestLock ║ 
 ╚════ ═══════╩═══════════════════════════════╩══════════ ══════════╩═══════════════════════════════════════ ══╝

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

Wenn wir nun das erste Abfragefenster in folgendes ä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

Wir werden diese Ausgabe aus der Abfrage blockierender Transaktionen sehen:

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

1 - nicht ganz wahr. Es gibt den Prozedur-Cache, der die für die Sperre verantwortliche Anweisung enthalten kann. Es ist jedoch möglicherweise nicht einfach festzustellen, 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;

Die Ergebnisse dieser Abfrage ermöglichen es Ihnen möglicherweise , den Schuldigen zu finden. Beachten Sie jedoch, dass das Überprüfen des Prozedurcaches auf ein ausgelastetes System sehr anspruchsvoll sein kann.

2SQL Server 2016 und höher bietet den Query Store , der den vollständigen Verlauf der ausgeführten Abfragen beibehält.

15
Max Vernon

Als Ergänzung zu Max 'Antwort 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 entstanden ist - was äußerst nützlich ist.

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

6
Kin Shah