Come identificare quale query sta riempiendo il registro delle transazioni tempdb?


65

Vorrei sapere come identificare la query esatta o il proc memorizzato che sta effettivamente riempiendo il registro delle transazioni del database TEMPDB.



Sono nuovo di questo sito e non sono sicuro di come modificare il post. Non ho accesso a PROD per fornire ulteriori informazioni. Tutto ciò che sento da PROD DBA è che il tuo codice sta riempiendo il tempdb! Esistono delle migliori pratiche di codifica da seguire per assicurarsi che il nostro codice non riempia il registro di tempdb?

@prasanth Dovrai iscriverti a questo sito con il tuo stesso openid per apportare modifiche alla tua domanda qui. Dipende da cosa sta facendo il tuo codice sul perché sta usando tempdb. Il piano di esecuzione dovrebbe mostrare cosa sta facendo e se pubblichi il codice effettivo, possiamo aiutarti a migliorarlo.
Cade Roux,

@CadeRoux Penso che stia cercando di identificare la query (o le query), non cercando di capire perché una specifica query nota stia causando il problema.
Aaron Bertrand

@AaronBertrand sì, ma il commento sembra indicare che vuole le migliori pratiche per la codifica.
Cade Roux,

Risposte:


73

Da 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;

MODIFICARE

Come ha sottolineato Martin in un commento, questo non troverà transazioni attive che occupano spazio in tempdb, ma troverà solo query attive che attualmente stanno utilizzando lo spazio (e probabilmente i colpevoli per l'utilizzo del registro corrente). Quindi potrebbe esserci una transazione aperta ma la query effettiva che causa il problema non è più in esecuzione.

È possibile modificare l' inner joinon sys.dm_exec_requestsin a left outer join, quindi si restituiranno le righe per le sessioni che non eseguono attualmente query attive.

La query Martin ha pubblicato ...

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;

... identificherebbe le session_idtransazioni attive che occupano spazio nel registro, ma non sarebbe necessariamente in grado di determinare la query effettiva che ha causato il problema, poiché se non è in esecuzione ora non verrà acquisita nella query precedente per richieste attive. Potresti essere in grado di controllare in modo reattivo la query più recente utilizzando DBCC INPUTBUFFERma potrebbe non dirti cosa vuoi ascoltare. Puoi unire all'esterno in modo simile per catturare quelli attivi, ad esempio:

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;

È inoltre possibile utilizzare DMV sys.dm_db_session_space_usageper visualizzare l'utilizzo dello spazio complessivo per sessione (ma potrebbe non essere possibile ottenere risultati validi per la query; se la query non è attiva, ciò che si ottiene potrebbe non essere il vero colpevole).

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

Con tutte queste domande a tua disposizione, dovresti essere in grado di restringere chi sta usando tempdb e come, specialmente se le prendi nell'atto.

alcuni suggerimenti per ridurre al minimo l'utilizzo di tempdb

  1. usa meno tabelle #temp e variabili @table
  2. minimizzare la manutenzione simultanea dell'indice ed evitare l' SORT_IN_TEMPDBopzione se non è necessaria
  3. evitare cursori inutili; evitare i cursori statici se si ritiene che questo possa essere un collo di bottiglia, poiché i cursori statici utilizzano le tabelle di lavoro in tempdb - sebbene questo sia il tipo di cursore che consiglio sempre se tempdb non è un collo di bottiglia
  4. cerca di evitare gli spool (es. CTE di grandi dimensioni a cui si fa riferimento più volte nella query)
  5. non usare MARTE
  6. testare accuratamente l'uso dei livelli di isolamento snapshot / RCSI - non accenderlo per tutti i database poiché ti è stato detto che è meglio di NOLOCK (lo è, ma non è gratuito)
  7. in alcuni casi, potrebbe sembrare poco intuitivo, ma utilizzare più tabelle temporanee. es. suddividere una gigantesca query in parti può essere leggermente meno efficiente, ma se può evitare un enorme sversamento di memoria in tempdb perché la singola query più grande richiede una concessione di memoria troppo grande ...
  8. evitare di abilitare i trigger per operazioni in blocco
  9. evitare l'uso eccessivo di tipi LOB (tipi massimi, XML, ecc.) come variabili locali
  10. mantenere le transazioni brevi e dolci
  11. non impostare tempdb come database predefinito di tutti -

È inoltre possibile considerare che l'utilizzo del registro tempdb potrebbe essere causato da processi interni su cui si ha un controllo scarso o nullo, ad esempio posta elettronica del database, notifiche di eventi, notifiche di query e broker di servizi utilizzano tutti in qualche modo tempdb. Puoi smettere di usare queste funzionalità, ma se le stai usando non puoi dettare come e quando usano tempdb.


Grazie per il link Aaron. In generale, ci sono delle migliori pratiche di codifica che devono essere seguite per evitare la compilazione dei registri transazionali TEMPDB?

2
Hmm, l'ho appena provato e non ha trovato la mia sessione offensiva anche se si session_idpresenta con la seguente query 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. La domanda che mi aspettavo di trovare era dopo aver eseguito il seguenteBEGIN TRAN CREATE TABLE #T(X CHAR(8000)) INSERT INTO #T SELECT name FROM sys.objects
Martin Smith,

@Martin: Notato che c'è un @@ SPID nel cte, che limiterebbe i risultati alla sessione corrente. Se vuoi che si estenda su tutte le sessioni, rimuovilo.
Ben giovedì

@BenThul - Ho eseguito la query in un'altra connessione. Il @@SPIDè <>non =. dm_db_task_space_usageriporta 0per me lo spid con la transazione aperta per tutte le colonne. Mi chiedo se è necessario interrogarlo quando la richiesta è effettivamente in esecuzione piuttosto che inattiva con una transazione aperta.
Martin Smith,

@MartinSmith la query trova solo richieste attive, non transazioni attive. Quindi, se la query non è più in esecuzione, hai ragione, è possibile rintracciare utilizzando i DMV delle transazioni. Ma non saresti necessariamente in grado di capire la query che l'ha causata se non è più in esecuzione - lo stesso spid potrebbe aver emesso diverse altre dichiarazioni nella transazione corrente.
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

4

Grazie per questo post, probabilmente l'unico nel suo genere. Il mio test è stato semplice, crea una tabella temporanea e assicurati che venga visualizzato quando eseguo una delle query da questo post ... solo uno o due sono riusciti davvero. L'ho corretto per unirmi al T-SQL, l'ho ottimizzato per corse più lunghe e l'ho reso piuttosto utile. Fammi sapere se mi sono perso qualcosa ma finora hai ottenuto uno script automatizzato / in loop. Fornisce un modo per valutare quale query / SPID è l'autore del reato per un periodo di tempo utilizzando la query di deviazione standard (STDEV) di seguito.

Questo passa ogni 3 minuti per 40 volte, quindi 2 ore. Modifica i parametri come ritieni opportuno.

C'è un filtro WHERE> 50 pagine qui sotto che le persone potrebbero voler cancellare nel caso in cui tu abbia molte piccole tabelle. Altrimenti non colpirai quella sfumatura con il sotto come è ...

Godere!

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

Combinarlo con la risposta accettata è un modo conveniente per tenere traccia dell'attività eluding di tempdb. L'esecuzione tramite un'attività pianificata di SQL Agent lo manterrà in esecuzione anche se SSMS è chiuso. Grazie per la condivisione!
Lockszmith,

1

Sfortunatamente il log tempDB non può essere ricondotto direttamente agli ID sessione visualizzando i processi in esecuzione.

Riduci il file di registro tempDB a un punto in cui crescerà di nuovo in modo significativo. Quindi creare un evento esteso per acquisire la crescita del registro. Una volta che cresce di nuovo, puoi espandere l'evento esteso e visualizzare il file degli eventi del pacchetto. Aprire il file, aggiungere un filtro orario, un filtro del tipo di file (non si desidera includere i risultati del file di dati), quindi raggrupparlo per ID sessione in SSMS. Questo ti aiuterà a trovare i colpevoli mentre stai cercando gli ID di sessione con il maggior numero di gruppi. Ovviamente devi raccogliere ciò che è in esecuzione negli ID di sessione attraverso un altro processo o strumento. Forse qualcuno sa come ottenere la query dalla colonna query_hash e sarà abbastanza gentile da pubblicare la soluzione.

Risultati dell'evento esteso:

inserisci qui la descrizione dell'immagine

Script per creare l'evento esteso:

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.