Compartir a través de


Solución de problemas de consultas que parecen no terminar nunca en SQL Server

En este artículo se describen los pasos de solución de problemas del problema en el que tiene una consulta que parece que nunca se ha completado o que la realización puede tardar muchas horas o días.

¿Qué es una consulta sin fin?

Este documento se centra en las consultas que continúan ejecutándose o compilando, es decir, su CPU continúa aumentando. No se aplica a las consultas que están bloqueadas o esperando algún recurso que nunca se libera (la CPU permanece constante o cambia muy poco).

Importante

Si se deja una consulta para finalizar su ejecución, finalmente se completará. Puede tardar unos segundos, o puede tardar varios días.

El término nunca finalizado se usa para describir la percepción de una consulta que no se completa cuando, de hecho, la consulta se completará finalmente.

Identificación de una consulta sin fin

Para identificar si una consulta se está ejecutando o se bloquea continuamente en un cuello de botella, siga estos pasos:

  1. Ejecute la siguiente consulta:

    DECLARE @cntr int = 0
    
    WHILE (@cntr < 3)
    BEGIN
        SELECT TOP 10 s.session_id,
                        r.status,
                        r.wait_time,
                        r.wait_type,
                        r.wait_resource,
                        r.cpu_time,
                        r.logical_reads,
                        r.reads,
                        r.writes,
                        r.total_elapsed_time / (1000 * 60) 'Elaps M',
                        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,
                        COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid)) 
                        + N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,
                        r.command,
                        s.login_name,
                        s.host_name,
                        s.program_name,
                        s.last_request_end_time,
                        s.login_time,
                        r.open_transaction_count,
                        atrn.name as transaction_name,
                        atrn.transaction_id,
                        atrn.transaction_state
            FROM sys.dm_exec_sessions AS s
            JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id 
                    CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st
            LEFT JOIN (sys.dm_tran_session_transactions AS stran 
                 JOIN sys.dm_tran_active_transactions AS atrn
                    ON stran.transaction_id = atrn.transaction_id)
            ON stran.session_id =s.session_id
            WHERE r.session_id != @@SPID
            ORDER BY r.cpu_time DESC
    
        SET @cntr = @cntr + 1
    WAITFOR DELAY '00:00:05'
    END
    
  2. Compruebe la salida de ejemplo.

    • Los pasos de solución de problemas de este artículo se aplican específicamente cuando observa una salida similar a la siguiente en la que la CPU aumenta proporcionalmente con el tiempo transcurrido, sin tiempos de espera significativos. Es importante tener en cuenta que los cambios en logical_reads no son relevantes en este caso, ya que algunas solicitudes T-SQL enlazadas a CPU podrían no hacer ninguna lectura lógica en absoluto (por ejemplo, realizar cálculos o un WHILE bucle).

      session_id status cpu_time logical_reads wait_time wait_type
      56 en ejecución 7038 101000 0 NULL
      56 ejecutable 12040 301000 0 NULL
      56 en ejecución 17020 523000 0 NULL
    • Este artículo no es aplicable si observa un escenario de espera similar al siguiente en el que la CPU no cambia o cambia muy ligeramente, y la sesión está esperando un recurso.

      session_id status cpu_time logical_reads wait_time wait_type
      56 suspended 0 3 8312 LCK_M_U
      56 suspended 0 3 13318 LCK_M_U
      56 suspended 0 5 18331 LCK_M_U

    Para obtener más información, consulte Diagnóstico de esperas o cuellos de botella.

Tiempo de compilación largo

En raras ocasiones, es posible que observe que la CPU está aumentando continuamente con el tiempo, pero que no está controlada por la ejecución de consultas. En su lugar, podría controlarse mediante una compilación excesivamente larga (el análisis y la compilación de una consulta). En esos casos, compruebe la columna de salida transaction_name y busque un valor de sqlsource_transform. Este nombre de transacción indica una compilación.

Recopilación de datos de diagnóstico

Para recopilar datos de diagnóstico mediante SQL Server Management Studio (SSMS), siga estos pasos:

  1. Capture el XML del plan de ejecución de consultas estimado.

  2. Revise el plan de consulta para ver si hay indicaciones obvias de dónde puede proceder la lentitud. Entre los ejemplos típicos se incluyen:

    • Exámenes de tabla o índice (examine las filas estimadas).
    • Bucles anidados controlados por un conjunto de datos de tabla externa enorme.
    • Bucles anidados con una rama grande en el lado interno del bucle.
    • Colas de tabla.
    • Funciones de la SELECT lista que tardan mucho tiempo en procesar cada fila.
  3. Si la consulta se ejecuta rápidamente en cualquier momento, puede capturar las ejecuciones "rápidas " del plan de ejecución XML real que se va a comparar.

Método para revisar los planes recopilados

En esta sección se muestra cómo revisar los datos recopilados. Usará los varios planes de consulta XML (mediante la extensión *.sqlplan) recopilados en SQL Server 2016 SP1 y versiones posteriores.

Siga estos pasos para comparar los planes de ejecución:

  1. Abra un archivo de plan de ejecución de consultas guardado anteriormente (.sqlplan).

  2. Haga clic con el botón derecho en un área en blanco del plan de ejecución y seleccione Comparar plan de presentación.

  3. Elija el segundo archivo de plan de consulta que desea comparar.

  4. Busque flechas gruesas que indican un gran número de filas que fluyen entre operadores. A continuación, seleccione el operador antes o después de la flecha y compare el número de filas reales entre dos planes.

  5. Compare los planes segundo y tercero para ver si el mayor flujo de filas se produce en los mismos operadores.

    Este es un ejemplo:

    Comparar planes de consulta en SSMS.

Solución

  1. Asegúrese de que las estadísticas se actualicen para las tablas usadas en la consulta.

  2. Busque una recomendación de índice que falta en el plan de consulta y aplique cualquiera.

  3. Vuelva a escribir la consulta con el objetivo de simplificarla:

    • Use predicados más selectivos WHERE para reducir los datos procesados por adelantado.
    • Divida.
    • Seleccione algunas partes en tablas temporales y acompáñelas más adelante.
    • Quite TOP, EXISTSy FAST (T-SQL) en las consultas que se ejecutan durante mucho tiempo debido al objetivo de fila del optimizador. Como alternativa, puede usar la DISABLE_OPTIMIZER_ROWGOAL sugerencia . Para obtener más información, consulte Objetivos de fila no autorizados.
    • Evite usar expresiones de tabla comunes (CTE) en tales casos, ya que combinan instrucciones en una sola consulta grande.
  4. Pruebe a usar sugerencias de consulta para generar un plan mejor:

    • HASH JOIN o MERGE JOIN sugerencia
    • Sugerencia FORCE ORDER
    • Sugerencia FORCESEEK
    • RECOMPILE
    • USE PLAN N'<xml_plan>' si tiene un plan de consulta rápido que puede forzar
  5. Use Almacén de consultas (QDS) para forzar un buen plan conocido si existe dicho plan y si la versión de SQL Server admite Almacén de consultas.

Diagnóstico de esperas o cuellos de botella

Esta sección se incluye aquí como referencia en caso de que el problema no sea una consulta de conducción de CPU de larga duración. Puede usarlo para solucionar problemas de consultas que son largas debido a esperas.

Para optimizar una consulta que está esperando cuellos de botella, identifique cuánto tiempo es la espera y dónde está el cuello de botella (el tipo de espera). Una vez confirmado el tipo de espera, reduzca el tiempo de espera o elimine la espera por completo.

Para calcular el tiempo de espera aproximado, resta el tiempo de CPU (tiempo de trabajo) del tiempo transcurrido de una consulta. Normalmente, el tiempo de CPU es el tiempo de ejecución real y la parte restante de la duración de la consulta está esperando.

Ejemplos de cómo calcular la duración aproximada de la espera:

Tiempo transcurrido (ms) Tiempo de CPU (ms) Tiempo de espera (ms)
3200 3000 200
7080 1 000 6080

Identificación del cuello de botella o espera

  • Para identificar consultas históricas de larga espera (por ejemplo, >el 20 % del tiempo de espera total transcurrido es el tiempo de espera), ejecute la consulta siguiente. Esta consulta usa estadísticas de rendimiento para los planes de consulta almacenados en caché desde el inicio de SQL Server.

    SELECT t.text,
             qs.total_elapsed_time / qs.execution_count
             AS avg_elapsed_time,
             qs.total_worker_time / qs.execution_count
             AS avg_cpu_time,
             (qs.total_elapsed_time - qs.total_worker_time) / qs.execution_count
             AS avg_wait_time,
             qs.total_logical_reads / qs.execution_count
             AS avg_logical_reads,
             qs.total_logical_writes / qs.execution_count
             AS avg_writes,
             qs.total_elapsed_time
             AS cumulative_elapsed_time
    FROM sys.dm_exec_query_stats qs
             CROSS apply sys.Dm_exec_sql_text (sql_handle) t
    WHERE (qs.total_elapsed_time - qs.total_worker_time) / qs.total_elapsed_time
             > 0.2
    ORDER BY qs.total_elapsed_time / qs.execution_count DESC
    
  • Para identificar las consultas que se ejecutan actualmente con esperas de más de 500 ms, ejecute la consulta siguiente:

    SELECT r.session_id, r.wait_type, r.wait_time AS wait_time_ms
    FROM sys.dm_exec_requests r 
       JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id 
    WHERE wait_time > 500
    AND is_user_process = 1
    
  • Si puede recopilar un plan de consulta, compruebe WaitStats de las propiedades del plan de ejecución en SSMS:

    1. Ejecute la consulta con Incluir plan de ejecución real activado.
    2. Haga clic con el botón derecho en el operador de la izquierda en la pestaña Plan de ejecución.
    3. Seleccione Propiedades y, a continuación, propiedad WaitStats .
    4. Compruebe waitTimeMs y WaitType.
  • Si está familiarizado con los escenarios PSSDiag/SQLdiag o SQL LogScout LightPerf/GeneralPerf, considere la posibilidad de usar cualquiera de ellos para recopilar estadísticas de rendimiento e identificar consultas en espera en la instancia de SQL Server. Puede importar los archivos de datos recopilados y analizar los datos de rendimiento con SQL Nexus.

Referencias para ayudar a eliminar o reducir las esperas

Las causas y resoluciones de cada tipo de espera varían. No hay ningún método general para resolver todos los tipos de espera. Estos son los artículos para solucionar y resolver problemas comunes de tipo de espera:

Para obtener descripciones de muchos tipos de espera y lo que indican, consulte la tabla en Tipos de esperas.