Wie kann man feststellen, welche Abfrage das Tempdb-Transaktionslog füllt?

65

Ich möchte wissen, wie man die genaue Abfrage oder den gespeicherten Prozess identifiziert, der das Transaktionsprotokoll der TEMPDB-Datenbank tatsächlich auffüllt.

Prasanth
quelle
Ich bin neu auf dieser Site und nicht sicher, wie ich den Beitrag bearbeite. Ich habe keinen Zugriff auf PROD, um weitere Informationen zu geben. Alles, was ich von PROD DBA höre, ist, dass Ihr Code die Tempdb auffüllt! Gibt es bewährte Codierungsmethoden, die befolgt werden müssen, um sicherzustellen, dass unser Code das Tempdb-Protokoll nicht ausfüllt?
@prasanth Sie müssen sich mit derselben OpenID für diese Site anmelden, um hier Änderungen an Ihrer Frage vornehmen zu können. Es hängt davon ab, was Ihr Code tut, um herauszufinden, warum er tempdb verwendet. Der Ausführungsplan sollte zeigen, was er tut, und wenn Sie den eigentlichen Code veröffentlichen, können wir ihn verbessern.
Cade Roux
@CadeRoux Ich glaube, er versucht, die Abfrage (oder Abfragen) zu identifizieren, ohne herauszufinden, warum eine bestimmte, bekannte Abfrage das Problem verursacht.
Aaron Bertrand
@AaronBertrand Ja, aber der Kommentar scheint darauf hinzudeuten, dass er Best Practices für das Codieren möchte.
Cade Roux

Antworten:

73

Von http://www.sqlservercentral.com/scripts/tempdb/72007/

;WITH task_space_usage AS (
    -- SUM alloc/delloc pages
    SELECT session_id,
           request_id,
           SUM(internal_objects_alloc_page_count) AS alloc_pages,
           SUM(internal_objects_dealloc_page_count) AS dealloc_pages
    FROM sys.dm_db_task_space_usage WITH (NOLOCK)
    WHERE session_id <> @@SPID
    GROUP BY session_id, request_id
)
SELECT TSU.session_id,
       TSU.alloc_pages * 1.0 / 128 AS [internal object MB space],
       TSU.dealloc_pages * 1.0 / 128 AS [internal object dealloc MB space],
       EST.text,
       -- Extract statement from sql text
       ISNULL(
           NULLIF(
               SUBSTRING(
                 EST.text, 
                 ERQ.statement_start_offset / 2, 
                 CASE WHEN ERQ.statement_end_offset < ERQ.statement_start_offset 
                  THEN 0 
                 ELSE( ERQ.statement_end_offset - ERQ.statement_start_offset ) / 2 END
               ), ''
           ), EST.text
       ) AS [statement text],
       EQP.query_plan
FROM task_space_usage AS TSU
INNER JOIN sys.dm_exec_requests ERQ WITH (NOLOCK)
    ON  TSU.session_id = ERQ.session_id
    AND TSU.request_id = ERQ.request_id
OUTER APPLY sys.dm_exec_sql_text(ERQ.sql_handle) AS EST
OUTER APPLY sys.dm_exec_query_plan(ERQ.plan_handle) AS EQP
WHERE EST.text IS NOT NULL OR EQP.query_plan IS NOT NULL
ORDER BY 3 DESC;

BEARBEITEN

Wie Martin in einem Kommentar ausführte, werden hier keine aktiven Transaktionen gefunden , die Speicherplatz in tempdb belegen. Es werden nur aktive Abfragen gefunden , die derzeit dort Speicherplatz belegen (und wahrscheinlich die Ursache für die aktuelle Protokollnutzung). Es könnte also eine offene Transaktion geben, aber die eigentliche Abfrage, die das Problem verursacht, wird nicht mehr ausgeführt.

Sie könnten die Änderung inner joinauf , sys.dm_exec_requestsum ein left outer join, dann werden Sie Zeilen für Sitzungen zurück , die derzeit nicht aktiv Abfragen ausgeführt werden .

Die Abfrage, die Martin gepostet hat ...

SELECT database_transaction_log_bytes_reserved,session_id 
  FROM sys.dm_tran_database_transactions AS tdt 
  INNER JOIN sys.dm_tran_session_transactions AS tst 
  ON tdt.transaction_id = tst.transaction_id 
  WHERE database_id = 2;

... würde session_ids mit aktiven Transaktionen identifizieren , die Protokollspeicher belegen, aber Sie wären nicht in der Lage, die tatsächliche Abfrage zu ermitteln, die das Problem verursacht hat, da sie in der obigen Abfrage für nicht erfasst wird, wenn sie jetzt nicht ausgeführt wird aktive Anfragen. Möglicherweise können Sie die aktuellste Abfrage mit reaktiv überprüfen DBCC INPUTBUFFER, sie sagt Ihnen jedoch möglicherweise nicht, was Sie hören möchten. Sie können einen Outer Join auf ähnliche Weise ausführen, um diejenigen zu erfassen, die aktiv ausgeführt werden. Beispiel:

SELECT tdt.database_transaction_log_bytes_reserved,tst.session_id,
       t.[text], [statement] = COALESCE(NULLIF(
         SUBSTRING(
           t.[text],
           r.statement_start_offset / 2,
           CASE WHEN r.statement_end_offset < r.statement_start_offset
             THEN 0
             ELSE( r.statement_end_offset - r.statement_start_offset ) / 2 END
         ), ''
       ), t.[text])
     FROM sys.dm_tran_database_transactions AS tdt
     INNER JOIN sys.dm_tran_session_transactions AS tst
     ON tdt.transaction_id = tst.transaction_id
         LEFT OUTER JOIN sys.dm_exec_requests AS r
         ON tst.session_id = r.session_id
         OUTER APPLY sys.dm_exec_sql_text(r.plan_handle) AS t
     WHERE tdt.database_id = 2;

Sie können auch die DMV verwenden sys.dm_db_session_space_usage, um die gesamte Speicherplatzauslastung pro Sitzung anzuzeigen (möglicherweise erhalten Sie jedoch auch hier keine gültigen Ergebnisse für die Abfrage zurück. Wenn die Abfrage nicht aktiv ist, ist das, was Sie zurückerhalten, möglicherweise nicht der eigentliche Schuldige).

;WITH s AS
(
    SELECT 
        s.session_id,
        [pages] = SUM(s.user_objects_alloc_page_count 
          + s.internal_objects_alloc_page_count) 
    FROM sys.dm_db_session_space_usage AS s
    GROUP BY s.session_id
    HAVING SUM(s.user_objects_alloc_page_count 
      + s.internal_objects_alloc_page_count) > 0
)
SELECT s.session_id, s.[pages], t.[text], 
  [statement] = COALESCE(NULLIF(
    SUBSTRING(
        t.[text], 
        r.statement_start_offset / 2, 
        CASE WHEN r.statement_end_offset < r.statement_start_offset 
        THEN 0 
        ELSE( r.statement_end_offset - r.statement_start_offset ) / 2 END
      ), ''
    ), t.[text])
FROM s
LEFT OUTER JOIN 
sys.dm_exec_requests AS r
ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.plan_handle) AS t
ORDER BY s.[pages] DESC;

Mit all diesen Abfragen sollten Sie eingrenzen können, wer Tempdb verwendet und wie, insbesondere, wenn Sie sie auf frischer Tat ertappen.

Einige Tipps zur Minimierung der Tempdb-Auslastung

  1. Verwenden Sie weniger # temporäre Tabellen und @ table-Variablen
  2. Minimieren Sie die gleichzeitige Indexwartung und vermeiden Sie die SORT_IN_TEMPDBOption, wenn sie nicht benötigt wird
  3. Vermeiden Sie unnötige Cursor. Vermeiden Sie statische Cursor, wenn Sie glauben, dass dies ein Engpass ist, da statische Cursor Arbeitstabellen in Tempdb verwenden. Dies ist jedoch der Cursortyp, den ich immer empfehle, wenn Tempdb kein Engpass ist
  4. Vermeiden Sie Spools (z. B. große CTEs, auf die in der Abfrage mehrfach verwiesen wird).
  5. benutze MARS nicht
  6. Testen Sie die Verwendung von Snapshot- / RCSI-Isolationsstufen gründlich - schalten Sie sie nicht nur für alle Datenbanken ein, da Sie erfahren haben, dass sie besser als NOLOCK sind (es ist, aber es ist nicht kostenlos).
  7. In einigen Fällen klingt dies möglicherweise nicht intuitiv, es werden jedoch mehr temporäre Tabellen verwendet. ZB kann das Aufteilen einer umfangreichen Abfrage in Teile etwas weniger effizient sein, aber wenn es einen großen Speicherverlust für Tempdb vermeiden kann, weil die einzelne, größere Abfrage eine zu große Speicherzuweisung erfordert ...
  8. Vermeiden Sie die Aktivierung von Triggern für Massenvorgänge
  9. Vermeiden Sie die übermäßige Verwendung von LOB-Typen (max. Typen, XML usw.) als lokale Variablen
  10. Halten Sie Transaktionen kurz und bündig
  11. tempdb nicht als Standarddatenbank festlegen -

Sie können auch bedenken, dass die Verwendung Ihres Tempdb-Protokolls durch interne Prozesse verursacht werden kann, über die Sie nur wenig oder gar keine Kontrolle haben. Beispielsweise verwenden Datenbank-E-Mails, Ereignisbenachrichtigungen, Abfragebenachrichtigungen und Service Broker Tempdb in irgendeiner Weise. Sie können die Verwendung dieser Funktionen beenden, aber wenn Sie sie verwenden, können Sie nicht vorschreiben, wie und wann sie Tempdb verwenden.

Aaron Bertrand
quelle
Danke für den Link Aaron. Gibt es im Allgemeinen Best Practices für die Codierung, die befolgt werden müssen, um ein Ausfüllen der TEMPDB-Transaktionsprotokolle zu vermeiden?
2
Hmm, habe das gerade getestet und meine anstößige Sitzung nicht gefunden, obwohl die session_idmit der folgenden Abfrage angezeigt wird SELECT database_transaction_log_bytes_reserved,session_id FROM sys.dm_tran_database_transactions tdt JOIN sys.dm_tran_session_transactions tst ON tdt.transaction_id = tst.transaction_id WHERE database_id = 2. Die Abfrage, die ich erwartete zu finden, war nach dem Ausführen der folgendenBEGIN TRAN CREATE TABLE #T(X CHAR(8000)) INSERT INTO #T SELECT name FROM sys.objects
Martin Smith
@Martin: Es wurde festgestellt, dass die CTE eine @@ SPID enthält, die die Ergebnisse auf die aktuelle Sitzung beschränkt. Wenn Sie möchten, dass es sich über alle Sitzungen erstreckt, entfernen Sie das.
Ben Thul,
@BenThul - Ich habe die Abfrage in einer anderen Verbindung ausgeführt. Das @@SPIDgeht <>nicht =. dm_db_task_space_usagemeldet 0für mich die spid mit der offenen transaktion für alle spalten. Fragen Sie sich, ob Sie es abfragen müssen, wenn die Anforderung tatsächlich ausgeführt wird, anstatt mit einer offenen Transaktion inaktiv zu sein.
Martin Smith
@MartinSmith Die Abfrage findet nur aktive Anforderungen, keine aktiven Transaktionen. Wenn die Abfrage also nicht mehr ausgeführt wird, haben Sie Recht, Sie können sie mithilfe der Transaktions-DMVs zurückverfolgen. Wenn die Abfrage nicht mehr ausgeführt wird, können Sie sie jedoch nicht unbedingt ermitteln. Möglicherweise hat dieselbe Spid mehrere andere Anweisungen in der aktuellen Transaktion ausgegeben.
Aaron Bertrand
5

https://social.msdn.microsoft.com/Forums/sqlserver/en-US/17d9f862-b9ae-42de-ada0-4229f56712dc/tempdb-log-filling-cannot-find-how-or-what?forum=sqldatabaseengine

 SELECT tst.[session_id],
            s.[login_name] AS [Login Name],
            DB_NAME (tdt.database_id) AS [Database],
            tdt.[database_transaction_begin_time] AS [Begin Time],
            tdt.[database_transaction_log_record_count] AS [Log Records],
            tdt.[database_transaction_log_bytes_used] AS [Log Bytes Used],
            tdt.[database_transaction_log_bytes_reserved] AS [Log Bytes Rsvd],
            SUBSTRING(st.text, (r.statement_start_offset/2)+1,
            ((CASE r.statement_end_offset
                    WHEN -1 THEN DATALENGTH(st.text)
                    ELSE r.statement_end_offset
            END - r.statement_start_offset)/2) + 1) AS statement_text,
            st.[text] AS [Last T-SQL Text],
            qp.[query_plan] AS [Last Plan]
    FROM    sys.dm_tran_database_transactions tdt
            JOIN sys.dm_tran_session_transactions tst
                ON tst.[transaction_id] = tdt.[transaction_id]
            JOIN sys.[dm_exec_sessions] s
                ON s.[session_id] = tst.[session_id]
            JOIN sys.dm_exec_connections c
                ON c.[session_id] = tst.[session_id]
            LEFT OUTER JOIN sys.dm_exec_requests r
                ON r.[session_id] = tst.[session_id]
            CROSS APPLY sys.dm_exec_sql_text (c.[most_recent_sql_handle]) AS st
            OUTER APPLY sys.dm_exec_query_plan (r.[plan_handle]) AS qp
    WHERE   DB_NAME (tdt.database_id) = 'tempdb'
    ORDER BY [Log Bytes Used] DESC
GO
Saurabh Sinha
quelle
4

Vielen Dank für diesen Beitrag, wahrscheinlich der einzige seiner Art. Mein Test war einfach, erstelle eine temporäre Tabelle und stelle sicher, dass sie angezeigt wird, wenn ich eine der Abfragen aus diesem Beitrag ausführe ... nur ein oder zwei waren wirklich erfolgreich. Ich habe es korrigiert, um es mit T-SQL zu verbinden, es für längere Läufe optimiert und es ziemlich nützlich gemacht. Lassen Sie mich wissen, wenn ich etwas verpasst habe, Sie aber bisher ein automatisiertes Skript erhalten haben. Mithilfe der folgenden STDEV-Abfrage (Standardabweichung) können Sie beurteilen, welche Abfrage / SPID über einen bestimmten Zeitraum der Täter ist.

Dies läuft 40 Mal alle 3 Minuten, also 2 Stunden. Ändern Sie die Parameter nach Bedarf.

Darunter befindet sich ein WHERE> 50-Seiten-Filter, den die Benutzer möglicherweise löschen möchten, falls Sie viele kleine Tabellen haben. Andernfalls werden Sie diese Nuance mit dem Folgenden nicht erfassen, wie es ist ...

Genießen!

DECLARE @minutes_apart INT; SET @minutes_apart = 3
DECLARE @how_many_times INT; SET @how_many_times = 40
--DROP TABLE tempdb..TempDBUsage
--SELECT * FROM tempdb..TempDBUsage
--SELECT session_id, STDEV(pages) stdev_pages FROM tempdb..TempDBUsage GROUP BY session_id HAVING STDEV(pages) > 0 ORDER BY stdev_pages DESC

DECLARE @delay_string NVARCHAR(8); SET @delay_string = '00:' + RIGHT('0'+ISNULL(CAST(@minutes_apart AS NVARCHAR(2)), ''),2) + ':00'
DECLARE @counter INT; SET @counter = 1

SET NOCOUNT ON
if object_id('tempdb..TempDBUsage') is null
    begin
    CREATE TABLE tempdb..TempDBUsage (
        session_id INT, pages INT, num_reads INT, num_writes INT, login_time DATETIME, last_batch DATETIME,
        cpu INT, physical_io INT, hostname NVARCHAR(64), program_name NVARCHAR(128), text NVARCHAR (MAX)
    )
    end
else
    begin
        PRINT 'To view the results run this:'
        PRINT 'SELECT * FROM tempdb..TempDBUsage'
        PRINT 'OR'
        PRINT 'SELECT session_id, STDEV(pages) stdev_pages FROM tempdb..TempDBUsage GROUP BY session_id HAVING STDEV(pages) > 0 ORDER BY stdev_pages DESC'
        PRINT ''
        PRINT ''
        PRINT 'Otherwise manually drop the table by running the following, then re-run the script:'
        PRINT 'DROP TABLE tempdb..TempDBUsage'
        RETURN
    end
--GO
TRUNCATE TABLE tempdb..TempDBUsage
PRINT 'To view the results run this:'; PRINT 'SELECT * FROM tempdb..TempDBUsage'
PRINT 'OR'; PRINT 'SELECT session_id, STDEV(pages) stdev_pages FROM tempdb..TempDBUsage GROUP BY session_id HAVING STDEV(pages) > 0 ORDER BY stdev_pages DESC'
PRINT ''; PRINT ''

while @counter <= @how_many_times
begin
INSERT INTO tempdb..TempDBUsage (session_id,pages,num_reads,num_writes,login_time,last_batch,cpu,physical_io,hostname,program_name,text)
    SELECT PAGES.session_id, PAGES.pages, r.num_reads, r.num_writes, sp.login_time, sp.last_batch, sp.cpu, sp.physical_io, sp.hostname, sp.program_name, t.text
    FROM sys.dm_exec_connections AS r
    LEFT OUTER JOIN master.sys.sysprocesses AS sp on sp.spid=r.session_id
    OUTER APPLY sys.dm_exec_sql_text(r.most_recent_sql_handle) AS t
    LEFT OUTER JOIN (
        SELECT s.session_id, [pages] = SUM(s.user_objects_alloc_page_count + s.internal_objects_alloc_page_count) 
        FROM sys.dm_db_session_space_usage AS s
        GROUP BY s.session_id
        HAVING SUM(s.user_objects_alloc_page_count + s.internal_objects_alloc_page_count) > 0
    ) PAGES ON PAGES.session_id = r.session_id
    WHERE PAGES.session_id IS NOT NULL AND PAGES.pages > 50
    ORDER BY PAGES.pages DESC;
PRINT CONVERT(char(10), @counter) + ': Ran at: ' + CONVERT(char(30), GETDATE())
SET @counter = @counter + 1
waitfor delay @delay_string
end
Joe Zee
quelle
Wenn Sie dies mit der akzeptierten Antwort kombinieren, können Sie die ausweichende Tempdb-Aktivität bequem nachverfolgen. Wenn Sie dies über einen von SQL Agent geplanten Task ausführen, wird dies auch dann ausgeführt, wenn SSMS geschlossen wird. Danke für das Teilen!
Schlosser
1

Leider kann das tempDB-Protokoll nicht direkt auf die Sitzungs-IDs zurückgeführt werden, indem laufende Prozesse angezeigt werden.

Verkleinern Sie die tempDB-Protokolldatei bis zu einem Punkt, an dem sie wieder erheblich wächst. Erstellen Sie dann ein erweitertes Ereignis, um das Protokollwachstum zu erfassen. Sobald es wieder wächst, können Sie das erweiterte Ereignis erweitern und die Paketereignisdatei anzeigen. Öffnen Sie die Datei, fügen Sie einen Zeitfilter und einen Dateitypfilter hinzu (die Ergebnisse der Datendatei sollen nicht enthalten sein), und gruppieren Sie sie dann nach Sitzungs-ID in SSMS. Dies wird Ihnen helfen, die Schuldigen zu finden, während Sie nach Sitzungs-IDs mit den meisten Gruppen-Bys suchen. Natürlich müssen Sie über einen anderen Prozess oder ein anderes Tool erfassen, was in den Sitzungs-IDs ausgeführt wird. Vielleicht weiß jemand, wie man die Abfrage aus der Spalte query_hash abruft, und ist so freundlich, die Lösung zu veröffentlichen.

Erweiterte Veranstaltungsergebnisse:

Bildbeschreibung hier eingeben

Skript zum Erstellen des erweiterten Ereignisses:

CREATE EVENT SESSION [tempdb_file_size_changed] ON SERVER ADD EVENT 
sqlserver.database_file_size_change(SET collect_database_name=(1)ACTION(sqlserver.client_app_name,sqlserver.client_hostname,sqlserver.is_system,sqlserver.query_hash,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.username) WHERE ([database_id]=(2))) ADD TARGETpackage0.event_file(SET filename=N'C:\ExtendedEvents\TempDBGrowth.xel',max_file_size=(100),max_rollover_files=(25)) WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=1 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=ON)
Tequila
quelle