¿Cómo identificar qué consulta está llenando el registro de transacciones tempdb?

65

Me gustaría saber cómo identificar la consulta exacta o el proceso almacenado que en realidad está llenando el registro de transacciones de la base de datos TEMPDB.

prasanth
fuente
Soy nuevo en este sitio y no estoy seguro de cómo editar la publicación. No tengo acceso a PROD para dar más información. ¡Todo lo que escucho de PROD DBA es que su código está llenando el tempdb! ¿Se deben seguir las mejores prácticas de codificación para garantizar que nuestro código no llene el registro de tempdb?
@prasanth Tendrá que registrarse en este sitio con su mismo openid para modificar su pregunta aquí. Depende de lo que esté haciendo su código y de por qué está usando tempdb. El plan de ejecución debe mostrar lo que está haciendo, y si publica el código real, podemos ayudarlo a mejorarlo.
Cade Roux
@CadeRoux Creo que está tratando de identificar la consulta (o consultas), no tratando de averiguar por qué una consulta específica y conocida está causando el problema.
Aaron Bertrand
@AaronBertrand, sí, pero el comentario parece indicar que quiere las mejores prácticas para la codificación.
Cade Roux

Respuestas:

73

De 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 señaló en un comentario, esto no encontraría transacciones activas que ocupan espacio en tempdb, solo encontrará consultas activas que actualmente utilizan espacio allí (y probablemente los culpables del uso actual del registro). Por lo tanto, podría haber una transacción abierta, pero la consulta real que causa el problema ya no se está ejecutando.

Puede cambiar el inner joinencendido sys.dm_exec_requestsa a left outer join, luego devolverá filas para las sesiones que actualmente no están ejecutando consultas activamente.

La consulta que Martin publicó ...

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;

... identificaría correos session_idelectrónicos con transacciones activas que están ocupando espacio de registro, pero no necesariamente podría determinar la consulta real que causó el problema, ya que si no se está ejecutando ahora no se capturará en la consulta anterior para solicitudes activas Es posible que pueda verificar de manera reactiva la consulta más reciente utilizando, DBCC INPUTBUFFERpero es posible que no le diga lo que desea escuchar. Puede unirse externamente de manera similar para capturar a aquellos que se ejecutan activamente, por ejemplo:

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;

También puede usar el DMV sys.dm_db_session_space_usagepara ver la utilización general del espacio por sesión (pero, de nuevo, es posible que no obtenga resultados válidos para la consulta; si la consulta no está activa, lo que recupere puede no ser el verdadero culpable).

;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 todas estas consultas a su disposición, debería poder limitar quién está usando tempdb y cómo, especialmente si las atrapa en el acto.

algunos consejos para minimizar la utilización de tempdb

  1. use menos tablas #temp y variables @table
  2. minimizar el mantenimiento simultáneo del índice y evitar la SORT_IN_TEMPDBopción si no es necesaria
  3. evitar cursores innecesarios; evite los cursores estáticos si cree que esto puede ser un cuello de botella, ya que los cursores estáticos usan tablas de trabajo en tempdb, aunque este es el tipo de cursor que siempre recomiendo si tempdb no es un cuello de botella
  4. trate de evitar los carretes (por ejemplo, CTE grandes a los que se hace referencia varias veces en la consulta)
  5. no uses MARS
  6. Pruebe a fondo el uso de los niveles de aislamiento de instantáneas / RCSI: no solo lo active para todas las bases de datos, ya que le han dicho que es mejor que NOLOCK (lo es, pero no es gratis)
  7. En algunos casos, puede parecer poco intuitivo, pero use más tablas temporales. por ejemplo, dividir una consulta enorme en partes puede ser un poco menos eficiente, pero si puede evitar un derrame de memoria enorme en tempdb porque la consulta única y más grande requiere una concesión de memoria demasiado grande ...
  8. evitar habilitar activadores para operaciones masivas
  9. evitar el uso excesivo de los tipos de LOB (tipos máximos, XML, etc.) como variables locales
  10. mantener las transacciones cortas y dulces
  11. no configure tempdb para que sea la base de datos predeterminada de todos -

También puede considerar que su uso del registro tempdb puede ser causado por procesos internos sobre los que tiene poco o ningún control, por ejemplo, el correo electrónico de la base de datos, las notificaciones de eventos, las notificaciones de consultas y el intermediario de servicios usan tempdb de alguna manera. Puede dejar de usar estas funciones, pero si las está usando no puede dictar cómo y cuándo usan tempdb.

Aaron Bertrand
fuente
Gracias por el enlace Aaron. En general, ¿hay alguna mejor práctica de codificación que deba seguirse para evitar el llenado de registros transaccionales TEMPDB?
2
Hmm, acabo de probar eso y no ha encontrado mi sesión ofensiva a pesar de que session_idaparece con la siguiente 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. La consulta que esperaba encontrar fue después de ejecutar lo siguienteBEGIN TRAN CREATE TABLE #T(X CHAR(8000)) INSERT INTO #T SELECT name FROM sys.objects
Martin Smith
@ Martin: Noté que hay un @@ SPID en el cte, lo que limitaría los resultados a la sesión actual. Si desea que abarque todas las sesiones, elimínelo.
Ben Thul
@BenThul: ejecuté la consulta en otra conexión. El no @@SPIDes . informes para el spid con la transacción abierta para todas las columnas para mí. Me pregunto si necesita consultarlo cuando la solicitud se está ejecutando en lugar de estar inactiva con una transacción abierta. <>=dm_db_task_space_usage0
Martin Smith
@MartinSmith, la consulta solo encuentra solicitudes activas, no transacciones activas. Entonces, si la consulta ya no se está ejecutando, tiene razón, puede realizar un seguimiento utilizando los DMV de transacción. Pero no necesariamente sería capaz de descubrir la consulta que la causó si ya no se está ejecutando; ese mismo spid podría haber emitido varias otras declaraciones en la transacción actual.
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
fuente
4

Gracias por esta publicación, probablemente la única de su tipo. Mi prueba fue simple, creé una tabla temporal y me aseguré de que aparezca cuando ejecuto cualquiera de las consultas de esta publicación ... solo una o dos realmente tuvieron éxito. Lo corregí para que se uniera al T-SQL, lo optimicé para tiradas más largas y lo hice bastante útil. Avíseme si me perdí algo, pero hasta ahora obtuvo un script automatizado / en bucle. Proporciona una forma de evaluar qué consulta / SPID es el delincuente durante un período de tiempo utilizando la consulta de desviación estándar (STDEV) a continuación.

Esto funciona cada 3 minutos durante 40 veces, es decir, 2 horas. Modifique los parámetros como mejor le parezca.

Hay un filtro WHERE> 50 páginas a continuación que la gente puede desear borrar en caso de que tenga muchas tablas pequeñas. De lo contrario, no captará ese matiz con el siguiente, ya que es ...

¡Disfrutar!

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
fuente
Combinar esto con la respuesta aceptada es una forma conveniente de rastrear la actividad eludida de tempdb. Ejecutar esto a través de una tarea programada del Agente SQL mantendrá esta ejecución incluso si SSMS está cerrado. ¡Gracias por compartir!
Cerrajero
1

Desafortunadamente, el registro de tempDB no se puede rastrear directamente hasta los ID de sesión al ver los procesos en ejecución.

Reduzca el archivo de registro tempDB a un punto donde crecerá significativamente de nuevo. Luego, cree un evento extendido para capturar el crecimiento del registro. Una vez que vuelva a crecer, puede expandir el evento extendido y ver el archivo del evento del paquete. Abra el archivo, agregue un filtro de tiempo, filtro de tipo de archivo (no desea que se incluyan los resultados del archivo de datos) y luego agrúpelo por ID de sesión en SSMS. Esto lo ayudará a encontrar al culpable (s) mientras busca identificadores de sesión con la mayor cantidad de grupos. Por supuesto, debe recopilar lo que se está ejecutando en las identificaciones de sesión a través de otro proceso o herramienta. Tal vez alguien sepa cómo obtener la consulta de la columna query_hash y tenga la amabilidad de publicar la solución.

Resultados del evento extendido:

ingrese la descripción de la imagen aquí

Script para crear el evento extendido:

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
fuente