SQL Server で終了しないように見えるクエリのトラブルシューティング

この記事では、クエリが完了しないと思われる問題のトラブルシューティング手順について説明します。または、クエリを完了するには数時間または数日かかる場合があります。

終わりのないクエリとは

このドキュメントでは、実行またはコンパイルを続けるクエリ、つまり CPU の増加を続けるクエリに焦点を当てています。 一部のリソースでブロックまたは待機しているクエリには適用されません (CPU は一定に保たれているか、ほとんど変更されません)。

重要

クエリの実行を完了するために残っている場合は、最終的に完了します。 数秒しかかからなかったり、数日かかる場合があります。

"終了しない" という用語は、クエリが実際に完了したときに完了しないという認識を表すために使用されます。

終わりのないクエリを識別する

クエリが継続的に実行されているか、ボトルネックでスタックしているかを特定するには、次の手順に従います。

  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. サンプル出力を確認します。

    • この記事のトラブルシューティング手順は、CPU が経過時間に比例して増加している次のような出力が、大幅な待機時間なしで発生する場合に特に適用されます。 一部の CPU バインド T-SQL 要求では論理読み取りがまったく行われない場合があるため(計算の実行やWHILEループなど)、この場合、logical_readsの変更は関係しないことに注意してください。

      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
    • この記事は、CPU がわずかに変化したり変更されたりせず、セッションがリソースで待機している次のような待機シナリオを観察する場合は適用されません。

      session_id status cpu_time logical_reads wait_time wait_type
      56 一時停止中 0 3 8312 LCK_M_U
      56 一時停止中 0 3 13318 LCK_M_U
      56 一時停止中 0 5 18331 LCK_M_U

    詳細については、 Diagnose の待機またはボトルネックを参照してください。

長いコンパイル時間

まれに、CPU が時間の経過と同時に継続的に増加しているが、クエリの実行によって決まるわけではないことがわかります。 代わりに、過度に長いコンパイル (クエリの解析とコンパイル) によって駆動される可能性があります。 そのような場合は、 transaction_name 出力列を確認し、 sqlsource_transformの値を探します。 このトランザクション名はコンパイルを示します。

診断データの収集

SQL Server Management Studio (SSMS) を使用して診断データを収集するには、次の手順に従います。

  1. 最新のクエリ実行プラン XML をキャプチャします。

  2. クエリ プランを確認して、遅さがどこから来る可能性があるかを明らかに示しているかどうかを確認します。 代表的な例は次のとおりです。

    • テーブルまたはインデックスのスキャン (推定行を参照)。
    • 巨大な外部テーブル データ セットによって駆動される入れ子になったループ。
    • ループの内側に大きな分岐がある入れ子になったループ。
    • テーブル スプール。
    • 各行の処理に長い時間がかかる SELECT リスト内の関数。
  3. クエリがいつでも高速に実行される場合は、比較する "高速" 実行 Actual XML 実行プラン をキャプチャできます。

収集されたプランを確認する方法

このセクションでは、収集されたデータを確認する方法について説明します。 SQL Server 2016 SP1 以降のビルドとバージョンで収集された複数の XML クエリ プラン (拡張機能 *.sqlplan を使用します。

実行プランを するには、次の手順に従います

  1. 以前に保存したクエリ実行プラン ファイル (.sqlplan) を開きます。

  2. 実行プランの空白領域を右クリックし、 Compare Showplan を選択します。

  3. 比較する 2 番目のクエリ プラン ファイルを選択します。

  4. 演算子間を流れる多数の行を示す太い矢印を探します。 次に、矢印の前または後の演算子を選択し、2 つのプランの actual 行の数を比較します。

  5. 2 番目と 3 番目のプランを比較して、同じ演算子で最大の行フローが発生するかどうかを確認します。

    次に例を示します。

    SSMS のクエリ プランを比較します。

解決方法

  1. クエリで使用されるテーブルの統計が更新されていることを確認します。

  2. クエリ プランで不足しているインデックスの推奨事項を探し、いずれかを適用します。

  3. クエリを簡略化する目的で書き直します。

    • より選択的な WHERE 述語を使用して、事前に処理されるデータを減らします。
    • 分割します。
    • 一時テーブルにいくつかの部分を選択し、後で結合します。
    • 行の目標を最適化するために非常に長い時間実行されるクエリの TOPEXISTS、および FAST (T-SQL) 削除します。 または、 DISABLE_OPTIMIZER_ROWGOAL hint を使用することもできます。 詳細については、「 Row Goals Gone Rogue」を参照してください。
    • ステートメントを 1 つの大きなクエリに結合する場合は、共通テーブル式 (CTE) を使用しないでください。
  4. クエリ ヒントを使用して、より適切なプランを作成してみてください。

    • HASH JOINまたはヒントMERGE JOIN
    • FORCE ORDER ヒント
    • FORCESEEK ヒント
    • RECOMPILE
    • 強制的に実行できる高速クエリ プランがある場合は、 PLAN N'<xml_plan>' を使用します
  5. クエリ ストア (QDS) を使用して、適切な既知のプランが存在する場合、および SQL Server のバージョンでクエリ ストアがサポートされている場合に、適切な既知のプランを強制します。

待機またはボトルネックを診断する

このセクションは、問題が実行時間の長い CPU 駆動クエリでない場合のリファレンスとしてここに含まれています。 これを使用して、待機が原因で長いクエリのトラブルシューティングを行うことができます。

ボトルネックを待機しているクエリを最適化するには、待機の長さとボトルネックの場所 (待機の種類) を特定します。 待機の種類が確認されたら待機時間を短縮するか、完全に待機を排除します。

おおよその待機時間を計算するには、クエリの経過時間から CPU 時間 (ワーカー時間) を減算します。 通常、CPU 時間は実際の実行時間であり、クエリの有効期間の残りの部分は待機しています。

おおよその待機時間を計算する方法の例:

経過時間 (ミリ秒) CPU 時間 (ms) 待機時間 (ミリ秒)
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
    
  • クエリ プランを収集できる場合は、SSMS の実行プランのプロパティからWaitStats を確認します。

    1. Include Actual Execution Plan on を使用してクエリを実行します。
    2. [実行プラン]タブで、左端の演算子右クリックします。
    3. Propertiesを選択しWaitStatsプロパティを選択します。
    4. WaitTimeMsWaitType を確認します。
  • PSSDiag/SQLdiag または SQL LogScout LightPerf/GeneralPerf シナリオに慣れている場合は、いずれかのシナリオを使用してパフォーマンス統計を収集し、SQL Server インスタンスで待機中のクエリを特定することを検討してください。 収集したデータ ファイルをインポートし、 SQL Nexus を使用してパフォーマンス データを分析できます。

待機の排除または削減に役立つ参照

各待機の種類の原因と解決策は異なります。 すべての待機の種類を解決する一般的な方法は 1 つもありません。 待機の種類に関する一般的な問題のトラブルシューティングと解決に関する記事を次に示します。

多くの待機の種類とそれらが示す内容の説明については、「待機の種類の表を参照してください。