Como identificar qual consulta está preenchendo o log de transações tempdb?

65

Gostaria de saber como identificar a consulta exata ou o processo armazenado que está realmente preenchendo o log transacional do banco de dados TEMPDB.

prasanth
fonte
Eu sou novo neste site e não sei como editar a postagem. Não tenho acesso ao PROD para fornecer mais informações. Tudo o que estou ouvindo do PROD DBA é que seu código está preenchendo o tempdb! Existem práticas recomendadas de codificação a serem seguidas para garantir que nosso código não preencha o log do tempdb?
@prasanth Você precisará se inscrever neste site com o mesmo código aberto para fazer alterações na sua pergunta aqui. Depende do que o seu código está fazendo e por que ele está usando o tempdb. O plano de execução deve mostrar o que está fazendo e, se você publicar o código real, podemos ajudar a melhorá-lo.
Cade Roux
@CadeRoux Acho que ele está tentando identificar a consulta (ou consultas), não tentando descobrir por que uma consulta específica e conhecida está causando o problema.
Aaron Bertrand
@AaronBertrand sim, mas o comentário parece indicar que ele deseja as melhores práticas de codificação.
Cade Roux

Respostas:

73

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

EDITAR

Como Martin apontou em um comentário, isso não encontraria transações ativas que ocupam espaço no tempdb, ele encontrará apenas consultas ativas que atualmente estão utilizando espaço lá (e provavelmente culpados pelo uso atual do log). Portanto, pode haver uma transação aberta, mas a consulta real que causa o problema não está mais sendo executada.

Você pode alterar para inner joinon sys.dm_exec_requestse left outer join, em seguida, retornará linhas para sessões que não estão atualmente executando consultas ativamente.

A consulta que Martin postou ...

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;

... identificaria session_ids com transações ativas que ocupam espaço de log, mas você não seria capaz de determinar necessariamente a consulta real que causou o problema, pois, se não estiver em execução agora, não será capturada na consulta acima para solicitações ativas. Você pode verificar reativamente a consulta mais recente usando, DBCC INPUTBUFFERmas ela pode não lhe dizer o que você deseja ouvir. Você pode ingressar externamente de maneira semelhante para capturar os que estão ativamente executando, por exemplo:

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;

Você também pode usar o DMV sys.dm_db_session_space_usagepara ver a utilização geral do espaço por sessão (mas, novamente, pode não receber resultados válidos para a consulta; se a consulta não estiver ativa, o que você recupera pode não ser o culpado).

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

Com todas essas consultas à sua disposição, você poderá restringir quem está usando o tempdb e como, especialmente se você as capturar em flagrante.

algumas dicas para minimizar a utilização do tempdb

  1. use menos tabelas #temp e variáveis ​​@table
  2. minimizar a manutenção simultânea do índice e evitar a SORT_IN_TEMPDBopção se não for necessária
  3. evite cursores desnecessários; evite cursores estáticos se você acha que isso pode ser um gargalo, pois os cursores estáticos usam tabelas de trabalho no tempdb - embora esse seja o tipo de cursor que eu sempre recomendo se o tempdb não for um gargalo
  4. tente evitar spools (por exemplo, CTEs grandes que são referenciadas várias vezes na consulta)
  5. não use MARS
  6. teste minuciosamente o uso dos níveis de isolamento de snapshot / RCSI - não ative-o em todos os bancos de dados, já que lhe disseram que é melhor que o NOLOCK (é, mas não é gratuito)
  7. em alguns casos, pode parecer pouco intuitivo, mas use mais tabelas temporárias. por exemplo, dividir uma consulta enorme em partes pode ser um pouco menos eficiente, mas se puder evitar um grande derramamento de memória para o tempdb, porque a consulta única e maior exige uma concessão de memória muito grande ...
  8. evite ativar gatilhos para operações em massa
  9. evitar o uso excessivo de tipos de LOB (tipos máximos, XML, etc) como variáveis ​​locais
  10. mantenha as transações curtas e agradáveis
  11. não defina tempdb como o banco de dados padrão de todos -

Você também pode considerar que o uso do log tempdb pode ser causado por processos internos sobre os quais você tem pouco ou nenhum controle - por exemplo, correio do banco de dados, notificações de eventos, notificações de consulta e intermediário de serviço, todos usam o tempdb de alguma forma. Você pode parar de usar esses recursos, mas se os estiver usando, não poderá determinar como e quando eles usam o tempdb.

Aaron Bertrand
fonte
Obrigado pelo link Aaron. Em geral, existem práticas recomendadas de codificação que precisam ser seguidas para evitar o preenchimento dos logs transacionais do TEMPDB?
2
Hmm, acabei de testar isso e ele não encontrou minha sessão ofensiva, apesar de session_idaparecer com a seguinte consulta 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. A consulta que eu esperava encontrar era depois de executar o seguinteBEGIN TRAN CREATE TABLE #T(X CHAR(8000)) INSERT INTO #T SELECT name FROM sys.objects
Martin Smith
@ Martin: Observou que há um @@ SPID no cte, o que limitaria os resultados à sessão atual. Se você deseja que ele se estenda por todas as sessões, remova-o.
precisa
@ BenThul - eu executei a consulta em outra conexão. O não @@SPIDé . relatórios para o spid com a transação aberta para todas as colunas para mim. Pergunte-se se você precisa consultá-lo quando a solicitação está realmente em execução, em vez de ociosa com uma transação aberta. <>=dm_db_task_space_usage0
Martin Smith
@MartinSmith, a consulta encontra apenas solicitações ativas, não transações ativas. Portanto, se a consulta não estiver mais em execução, você está certo, poderá rastrear usando as DMVs da transação. Mas você não seria capaz de descobrir a consulta que a causou se ela não estiver mais em execução - esse mesmo spid pode ter emitido várias outras instruções na transação atual.
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
fonte
4

Obrigado por este post, provavelmente o único de seu tipo. Meu teste foi simples, crie uma tabela temporária e verifique se ela aparece quando executo qualquer uma das consultas desta postagem ... apenas uma ou duas foram realmente bem-sucedidas. Corrigi-o para ingressar no T-SQL, otimizei-o para execuções mais longas e o tornei bastante útil. Deixe-me saber se eu perdi alguma coisa, mas até agora você recebeu um script automatizado / em loop. Ele fornece uma maneira de avaliar qual consulta / SPID é o ofensor por um período de tempo usando a consulta de desvio padrão (STDEV) abaixo.

Isso é executado a cada 3 minutos por 40 vezes, portanto, 2 horas. Modifique os parâmetros como achar melhor.

Existe um filtro WHERE> 50 páginas abaixo que as pessoas podem querer limpar caso você tenha muitas tabelas pequenas. Caso contrário, você não vai capturar essa nuance com o abaixo, pois é ...

Desfrutar!

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
fonte
Combinar isso com a resposta aceita é uma maneira conveniente de rastrear atividades ilegais do tempdb. A execução disso por meio de uma tarefa agendada do SQL Agent manterá a execução, mesmo se o SSMS estiver fechado. Obrigado por compartilhar!
Lockszmith # 11/18
1

Infelizmente, o log tempDB não pode ser rastreado diretamente para os IDs da sessão, exibindo os processos em execução.

Reduza o arquivo de log tempDB para um ponto em que ele crescerá significativamente novamente. Em seguida, crie um evento estendido para capturar o crescimento do log. Depois que crescer novamente, você poderá expandir o evento estendido e visualizar o arquivo de evento do pacote. Abra o arquivo, adicione um filtro de hora, filtro de tipo de arquivo (você não deseja que os resultados do arquivo de dados sejam incluídos) e agrupe-o por ID de sessão no SSMS. Isso o ajudará a encontrar o (s) culpado (s) enquanto procura os IDs de sessão com mais grupos por. Obviamente, você precisa coletar o que está sendo executado nos IDs da sessão por meio de outro processo ou ferramenta. Talvez alguém saiba como obter a consulta da coluna query_hash e tenha a gentileza de postar a solução.

Resultados do evento estendido:

insira a descrição da imagem aqui

Script para criar o evento estendido:

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
fonte