Share via


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

En este artículo se describen los pasos para solucionar el problema en el que tiene una consulta que parece no completarse nunca, o para que se complete puede tardar muchas horas o días.

¿Qué es una consulta interminable?

Este documento se centra en las consultas que continúan ejecutándose o compilando, es decir, su CPU sigue aumentando. No se aplica a las consultas que están bloqueadas o esperando en algún recurso que nunca se libera (la CPU sigue siendo 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 never-ending se usa para describir la percepción de que una consulta no se completa cuando, de hecho, la consulta se completará finalmente.

Identificación de una consulta sin fin

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

  1. Ejecute la consulta siguiente:

    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 se 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 pertinentes en este caso, ya que es posible que algunas solicitudes T-SQL enlazadas a la CPU no realicen ninguna lectura lógica (por ejemplo, realizar cálculos o un WHILE bucle).

      session_id status cpu_time logical_reads wait_time wait_type
      56 Corriendo 7038 101000 0 NULL
      56 ejecutable 12040 301000 0 NULL
      56 Corriendo 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 en 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 aumenta continuamente con el tiempo, pero eso no está controlado por la ejecución de consultas. En su lugar, podría estar controlada por una compilación excesivamente larga (análisis y 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 alguna indicación obvia de de dónde puede provenir la lentitud. Entre los ejemplos típicos se incluyen:

    • Exámenes de tabla o índice (examine las filas estimadas).
    • Bucles anidados controlados por un enorme conjunto de datos de tabla externa.
    • Bucles anidados con una rama grande en el lado interno del bucle.
    • Grupos de tablas.
    • 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 indiquen 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 en dos planes.

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

    Aquí le mostramos un ejemplo:

    Compare los planes de consulta en SSMS.

Solución

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

  2. Busque una recomendación de índice que falte 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.
    • Desmoronarse.
    • Seleccione algunas partes en tablas temporales y únase a ellas 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_ROWGOALsugerencia. Para obtener más información, vea Row Goals Gone Rogue.
    • Evite usar expresiones de tabla comunes (CTE) en casos como cuando combinen instrucciones en una sola consulta grande.
  4. Pruebe a usar sugerencias de consulta para generar un mejor plan:

    • HASH JOIN o MERGE JOIN sugerencia
    • FORCE ORDER Pista
    • FORCESEEK Pista
    • 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 plan conocido si existe este tipo de 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 espera 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, reste 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 1000 6080

Identificar el cuello de botella o la espera

  • Para identificar consultas históricas de larga espera (por ejemplo, >el 20 % del tiempo total transcurrido es 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 superiores a 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, la 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 las 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 problemas comunes de tipo de espera:

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