Udostępnij za pośrednictwem


Rozwiązywanie problemów z zapytaniami, które wydają się nigdy nie kończyć w programie SQL Server

W tym artykule opisano kroki rozwiązywania problemu polegające na tym, że masz zapytanie, które wydaje się nigdy nie zostać ukończone, lub ukończenie jego ukończenia może potrwać wiele godzin lub dni.

Co to jest niekończące się zapytanie?

Ten dokument koncentruje się na zapytaniach, które nadal są wykonywane lub kompilowane, czyli ich procesor CPU nadal rośnie. Nie ma zastosowania do zapytań, które są zablokowane lub oczekują na jakiś zasób, który nigdy nie jest zwalniany (procesor pozostaje stały lub zmienia się bardzo mało).

Ważne

Jeśli zapytanie pozostanie do zakończenia jego wykonywania, zostanie zakończone. Może to potrwać zaledwie kilka sekund lub może potrwać kilka dni.

Termin niekończący się jest używany do opisywania postrzegania zapytania, które nie kończy się w rzeczywistości, zapytanie zostanie ostatecznie ukończone.

Identyfikowanie niekończącego się zapytania

Aby określić, czy zapytanie jest stale wykonywane, czy zablokowane w wąskim gardło, wykonaj następujące kroki:

  1. Uruchom poniższe zapytanie:

    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. Sprawdź przykładowe dane wyjściowe.

    • Kroki rozwiązywania problemów w tym artykule mają zastosowanie w szczególności, gdy zauważysz dane wyjściowe podobne do następujących, w których procesor CPU zwiększa się proporcjonalnie wraz z upływem czasu bez znaczących czasów oczekiwania. Należy pamiętać, że w tym przypadku zmiany logical_reads nie są istotne, ponieważ niektóre żądania T-SQL powiązane z procesorem CPU mogą w ogóle nie wykonywać żadnych operacji odczytu logicznego (na przykład wykonywania obliczeń lub WHILE pętli).

      session_id status cpu_time logical_reads wait_time wait_type
      56 uruchomiono 7038 101000 0 NULL
      56 runnable 12040 301000 0 NULL
      56 uruchomiono 17020 523000 0 NULL
    • Ten artykuł nie ma zastosowania, jeśli obserwujesz scenariusz oczekiwania podobny do poniższego, w którym procesor CPU nie zmienia się lub zmienia się bardzo nieznacznie, a sesja czeka na zasób.

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

    Aby uzyskać więcej informacji, zobacz Diagnozowanie oczekiwań lub wąskich gardeł.

Długi czas kompilacji

W rzadkich przypadkach można zauważyć, że procesor CPU stale rośnie w czasie, ale nie jest napędzany przez wykonywanie zapytań. Zamiast tego może to być spowodowane nadmiernie długą kompilacją (analizowanie i kompilowanie zapytania). W takich przypadkach sprawdź kolumnę danych wyjściowych transaction_name i poszukaj wartości sqlsource_transform. Ta nazwa transakcji wskazuje kompilację.

Zbieranie danych diagnostycznych

Aby zebrać dane diagnostyczne przy użyciu programu SQL Server Management Studio (SSMS), wykonaj następujące kroki:

  1. Przechwyć szacowany kod XML planu wykonywania zapytania.

  2. Przejrzyj plan zapytania, aby sprawdzić, czy istnieją oczywiste wskazania, skąd może pochodzić spowolnienie. Typowe przykłady:

    • Skanowanie tabeli lub indeksu (spójrz na szacowane wiersze).
    • Zagnieżdżone pętle sterowane przez ogromny zewnętrzny zestaw danych tabeli.
    • Zagnieżdżone pętle z dużą gałęzią w wewnętrznej części pętli.
    • tabel.
    • Funkcje na SELECT liście, które zajmują dużo czasu na przetworzenie każdego wiersza.
  3. Jeśli zapytanie działa szybko w dowolnym momencie, możesz przechwycić "szybkie" wykonania rzeczywistego planu wykonywania XML do porównania.

Metoda przeglądania zebranych planów

W tej sekcji przedstawiono sposób przeglądania zebranych danych. Będzie ona używać wielu planów zapytań XML (przy użyciu rozszerzenia *.sqlplan) zebranych w programie SQL Server 2016 SP1 i nowszych kompilacjach i wersjach.

Wykonaj następujące kroki, aby porównać plany wykonywania:

  1. Otwórz wcześniej zapisany plik planu wykonywania zapytania (.sqlplan).

  2. Kliknij prawym przyciskiem myszy pusty obszar planu wykonania i wybierz pozycję Porównaj showplan.

  3. Wybierz drugi plik planu zapytania, który chcesz porównać.

  4. Poszukaj grubych strzałek, które wskazują dużą liczbę wierszy przepływających między operatorami. Następnie wybierz operator przed strzałką lub po nim i porównaj liczbę rzeczywistych wierszy w dwóch planach.

  5. Porównaj drugie i trzecie plany, aby sprawdzić, czy największy przepływ wierszy występuje w tych samych operatorach.

    Oto przykład:

    Porównaj plany zapytań w programie SSMS.

Rozwiązanie

  1. Upewnij się, że statystyki są aktualizowane dla tabel używanych w zapytaniu.

  2. Wyszukaj brakujące zalecenie dotyczące indeksu w planie zapytania i zastosuj dowolne.

  3. Zastąp ponownie zapytanie, aby uprościć je:

    • Użyj bardziej selektywnych WHERE predykatów, aby zmniejszyć ilość przetwarzanych danych z góry.
    • Rozbić go od siebie.
    • Wybierz niektóre części w tabelach tymczasowych i dołącz je później.
    • Usuń TOPwartości , EXISTSi FAST (T-SQL) w zapytaniach uruchamianych przez bardzo długi czas ze względu na cel wiersza optymalizatora. Alternatywnie możesz użyć DISABLE_OPTIMIZER_ROWGOAL wskazówki. Aby uzyskać więcej informacji, zobacz Row Goals Gone Rogue.
    • Unikaj używania typowych wyrażeń tabel (CTE) w takich przypadkach, jak łączą instrukcje w jednym dużym zapytaniu.
  4. Spróbuj użyć wskazówek dotyczących zapytań , aby utworzyć lepszy plan:

    • HASH JOIN lub MERGE JOIN wskazówka
    • FORCE ORDER aluzja
    • FORCESEEK aluzja
    • RECOMPILE
    • UŻYJ PLAN N'<xml_plan>' , jeśli masz szybki plan zapytania, który możesz wymusić
  5. Użyj magazynu zapytań (QDS), aby wymusić dobry plan, jeśli taki plan istnieje i czy wersja programu SQL Server obsługuje magazyn zapytań.

Diagnozowanie oczekiwań lub wąskich gardeł

Ta sekcja jest zawarta tutaj jako odwołanie w przypadku, gdy problem nie jest długotrwałym zapytaniem kierującym procesorEM CPU. Można go użyć do rozwiązywania problemów z zapytaniami, które są długotrwałe z powodu oczekiwania.

Aby zoptymalizować zapytanie oczekujące na wąskie gardła, zidentyfikuj czas oczekiwania i miejsce wąskiego gardła (typ oczekiwania). Po potwierdzeniu typu oczekiwania zmniejsz czas oczekiwania lub całkowicie zlikwiduj oczekiwanie.

Aby obliczyć przybliżony czas oczekiwania, odejmij czas procesora CPU (czas procesu roboczego) od czasu, który upłynął w zapytaniu. Zazwyczaj czas procesora CPU to rzeczywisty czas wykonywania, a pozostała część okresu istnienia zapytania oczekuje.

Przykłady obliczania przybliżonego czasu trwania oczekiwania:

Czas upływu (ms) Czas procesora CPU (ms) Czas oczekiwania (ms)
3200 3000 200
7080 1000 6080

Identyfikowanie wąskiego gardła lub oczekiwania

  • Aby zidentyfikować historyczne długotrwałe zapytania (na przykład >20% ogólnego czasu oczekiwania, czyli czas oczekiwania), uruchom następujące zapytanie. To zapytanie używa statystyk wydajności dla buforowanych planów zapytań od początku programu 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
    
  • Aby zidentyfikować aktualnie wykonywane zapytania z oczekiwaniami dłuższymi niż 500 ms, uruchom następujące zapytanie:

    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
    
  • Jeśli możesz zebrać plan zapytania, sprawdź wartości WaitStats z właściwości planu wykonywania w programie SSMS:

    1. Uruchom zapytanie z włączonym uwzględnij rzeczywisty plan wykonania .
    2. Kliknij prawym przyciskiem myszy operator najbardziej po lewej stronie na karcie Plan wykonania
    3. Wybierz pozycję Właściwości , a następnie właściwość WaitStats .
    4. Sprawdź wartości WaitTimeMs i WaitType.
  • Jeśli znasz scenariusze PSSDiag/SQLdiag lub SQL LogScout LightPerf/GeneralPerf, rozważ użycie jednej z nich do zbierania statystyk wydajności i identyfikowania oczekujących zapytań w wystąpieniu programu SQL Server. Zebrane pliki danych można zaimportować i przeanalizować dane wydajności za pomocą narzędzia SQL Nexus.

Odwołania pomagające wyeliminować lub zmniejszyć oczekiwania

Przyczyny i rozwiązania dla każdego typu oczekiwania różnią się. Nie ma jednej ogólnej metody rozpoznawania wszystkich typów oczekiwania. Poniżej przedstawiono artykuły umożliwiające rozwiązywanie typowych problemów z typem oczekiwania:

Opisy wielu typów oczekiwania i wskazywanych przez nie elementów można znaleźć w tabeli Typy oczekiwania.