Поделиться через


Устранение неполадок с запросами, которые, кажется, никогда не заканчиваются в SQL Server

В этой статье описаны действия по устранению неполадок, из-за которых запрос, который, кажется, никогда не завершается, или его завершение может занять много часов или дней.

Что такое бесконечный запрос?

В этом документе основное внимание уделяется запросам, которые продолжают выполняться или компилироваться, то есть их ЦП продолжает увеличиваться. Он не применяется к запросам, которые заблокированы или ожидают на каком-либо ресурсе, который никогда не освобождается (ЦП остается постоянным или изменяется очень мало).

Важно!

Если запросу осталось завершить выполнение, он в конечном итоге завершится. Это может занять всего несколько секунд, или может занять несколько дней.

Термин "бесконечный" используется для описания восприятия того, что запрос не завершается, когда на самом деле запрос в конечном итоге завершается.

Определение бесконечного запроса

Чтобы определить, выполняется ли запрос постоянно или зависает на узком месте, выполните следующие действия.

  1. Выполните следующий запрос:

    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. Проверьте пример выходных данных.

    • Действия по устранению неполадок, описанные в этой статье, особенно применимы, если вы заметили выходные данные, аналогичные приведенному ниже, когда загрузка ЦП увеличивается пропорционально затраченному времени без значительного времени ожидания. Важно отметить, что изменения в logical_reads этом случае не имеют значения, так как некоторые запросы T-SQL, привязанные к ЦП, могут вообще не выполнять никаких логических операций чтения (например, выполнение вычислений WHILE или цикл).

      session_id status cpu_time logical_reads wait_time wait_type
      56 Запущена 7038 101000 0 NULL
      56 готово к запуску 12040 301000 0 NULL
      56 Запущена 17020 523000 0 NULL
    • Эта статья неприменима, если вы наблюдаете сценарий ожидания, аналогичный приведенному ниже, когда ЦП не изменяется или изменяется незначительно, а сеанс ожидает ресурса.

      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

    Дополнительные сведения см. в разделе Диагностика ожиданий или узких мест.

Длительное время компиляции

В редких случаях вы можете наблюдать, что ЦП постоянно увеличивается с течением времени, но это не зависит от выполнения запроса. Вместо этого она может быть вызвана слишком длинной компиляцией (синтаксический анализ и компиляция запроса). В таких случаях проверка выходной столбец transaction_name и найдите значение sqlsource_transform. Это имя транзакции указывает на компиляцию.

Сбор диагностических данных

Чтобы собрать диагностические данные с помощью SQL Server Management Studio (SSMS), выполните следующие действия.

  1. Запишите XML-файл предполагаемого плана выполнения запроса .

  2. Просмотрите план запроса, чтобы узнать, есть ли очевидные признаки того, откуда может возникнуть задержка. Типичные примеры:

    • Сканирование таблиц или индексов (просмотрите предполагаемые строки).
    • Вложенные циклы, управляемые огромным набором данных внешней таблицы.
    • Вложенные циклы с большой ветвью во внутренней части цикла.
    • Очереди таблиц.
    • Функции в списке SELECT , обработка каждой строки которых занимает много времени.
  3. Если запрос выполняется быстро в любое время, можно записать "быстрые" выполнения Фактический план выполнения XML для сравнения.

Метод проверки собранных планов

В этом разделе показано, как просмотреть собранные данные. Он будет использовать несколько планов ЗАПРОСОВ XML (с расширением *.sqlplan), собранных в SQL Server 2016 с пакетом обновления 1 (SP1) и более поздних сборках и версиях.

Чтобы сравнить планы выполнения, выполните следующие действия.

  1. Откройте ранее сохраненный файл плана выполнения запроса (.sqlplan).

  2. Щелкните правой кнопкой мыши пустую область плана выполнения и выберите Сравнить план выполнения.

  3. Выберите второй файл плана запроса, который требуется сравнить.

  4. Найдите толстые стрелки, указывающие на большое количество строк, передаваемых между операторами. Затем выберите оператор до или после стрелки и сравните количество фактических строк в двух планах.

  5. Сравните второй и третий планы, чтобы узнать, происходит ли наибольший поток строк в одних и том же операторах.

    Пример:

    Сравнение планов запросов в SSMS.

Разрешение

  1. Убедитесь, что статистика обновлена для таблиц, используемых в запросе.

  2. Найдите отсутствующие рекомендации по индексу в плане запроса и примените ее.

  3. Перепишите запрос, чтобы упростить его:

    • Используйте более селективные WHERE предикаты, чтобы сократить объем обрабатываемых данных.
    • Разорвать его на части.
    • Выберите некоторые части во временные таблицы и соедините их позже.
    • Удалите TOP, EXISTSи FAST (T-SQL) в запросах, которые выполняются в течение очень длительного времени из-за цели строки оптимизатора. Кроме того, можно использовать подсказкуDISABLE_OPTIMIZER_ROWGOAL. Дополнительные сведения см. в разделе Row Goals Gone Rogue.
    • Избегайте использования общих табличных выражений (CTE) в таких случаях, когда они объединяют инструкции в один большой запрос.
  4. Попробуйте использовать указания запросов , чтобы создать лучший план:

    • HASH JOIN или MERGE JOIN подсказка
    • FORCE ORDER Подсказка
    • FORCESEEK Подсказка
    • RECOMPILE
    • ИСПОЛЬЗУЙТЕ PLAN N'<xml_plan>' , если у вас есть быстрый план запроса, который можно принудительно применить.
  5. Используйте хранилище запросов (QDS) для принудительного применения хорошо известного плана, если такой план существует и если версия SQL Server поддерживает хранилище запросов.

Диагностика ожиданий или узких мест

Этот раздел включен в качестве справки на случай, если проблема не является длительным запросом на управление ЦП. Его можно использовать для устранения неполадок с запросами, которые выполняются долго из-за ожиданий.

Чтобы оптимизировать запрос, ожидающий узких мест, определите, сколько времени ожидания и где находится узкое место (тип ожидания). После подтверждения типа ожидания сократите время ожидания или полностью исключите его.

Чтобы вычислить приблизительное время ожидания, вычесть время ЦП (время рабочей роли) из затраченного времени запроса. Как правило, время ЦП — это фактическое время выполнения, а оставшаяся часть времени существования запроса ожидается.

Примеры вычисления приблизительной продолжительности ожидания:

Затраченное время (мс) Время ЦП (мс) Время ожидания (мс)
3200 3000 200
7080 1000 6080

Определение узкого места или ожидания

  • Чтобы определить исторические запросы с длительным ожиданием (например, >20 % от общего затраченного времени составляет время ожидания), выполните следующий запрос. Этот запрос использует статистику производительности для кэшированных планов запросов с начала 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
    
  • Чтобы определить выполняемые в настоящее время запросы с ожиданиями более 500 мс, выполните следующий запрос:

    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
    
  • Если вы можете собрать план запроса, проверка WaitStats из свойств плана выполнения в SSMS:

    1. Выполните запрос с включением фактического плана выполнения в.
    2. Щелкните правой кнопкой мыши левый оператор на вкладке План выполнения .
    3. Выберите Свойства , а затем — Свойство WaitStats .
    4. Проверьте Значения WaitTimeMs и WaitType.
  • Если вы знакомы со сценариями PSSDiag/SQLdiag или SQL LogScout LightPerf/GeneralPerf, рассмотрите возможность использования любого из них для сбора статистики производительности и определения ожидающих запросов на экземпляре SQL Server. Вы можете импортировать собранные файлы данных и анализировать данные о производительности с помощью SQL Nexus.

Ссылки для устранения или сокращения ожиданий

Причины и способы их устранения для каждого типа ожидания различаются. Не существует единого общего метода для разрешения всех типов ожидания. Ниже приведены статьи по устранению неполадок и устранению распространенных проблем с типом ожидания:

Описание многих типов ожиданий и то, что они указывают, см. в таблице Типы ожиданий.