この5部構成のシリーズでは、SQLServerの行モードの並列プランの起動方法について詳しく説明します。この最初の部分では、並列実行の計画を準備する際の親タスク(コーディネーター)の役割について説明します。これには、各演算子の初期化と、実際の行数や経過時間などの実行時パフォーマンスデータを収集するための非表示のプロファイラーの追加が含まれます。
分析の具体的な基礎を提供するために、特定の並列クエリがどのように実行を開始するかを追跡します。公開されているStackOverflow 2013を使用しました データベース(ダウンロードの詳細)。より便利な場合は、小さいStackOverflow2010データセットに対して目的の平面形状を取得することもできます。同じリンクからダウンロードできます。非クラスター化インデックスを1つ追加しました:
CREATE NONCLUSTERED INDEX PP ON dbo.Posts ( PostTypeId ASC, CreationDate ASC );
私のテスト環境はSQLServer 2019 CU9 インスタンスに割り当てられた8コアと16GBのメモリを備えたラップトップ。互換性レベル150 排他的に使用されます。必要に応じて、目標計画を再現するのに役立つ詳細について説明します。行モードの並列実行の基本はSQLServer2005以降変更されていないため、以下の説明は広く適用できます。
テストクエリは、月と年でグループ化された質問と回答の総数を返します。
WITH MonthlyPosts AS ( SELECT P.PostTypeId, CA.TheYear, CA.TheMonth, Latest = MAX(P.CreationDate) FROM dbo.Posts AS P CROSS APPLY ( VALUES ( YEAR(P.CreationDate), MONTH(P.CreationDate) ) ) AS CA (TheYear, TheMonth) GROUP BY P.PostTypeId, CA.TheYear, CA.TheMonth ) SELECT rn = ROW_NUMBER() OVER ( ORDER BY Q.TheYear, Q.TheMonth), Q.TheYear, Q.TheMonth, LatestQuestion = Q.Latest, LatestAnswer = A.Latest FROM MonthlyPosts AS Q JOIN MonthlyPosts AS A ON A.TheYear = Q.TheYear AND A.TheMonth = Q.TheMonth WHERE Q.PostTypeId = 1 AND A.PostTypeId = 2 ORDER BY Q.TheYear, Q.TheMonth OPTION ( USE HINT ('DISALLOW_BATCH_MODE'), USE HINT ('FORCE_DEFAULT_CARDINALITY_ESTIMATION'), ORDER GROUP, MAXDOP 2 );
ヒントを使用して、特定の形状の行モードプランを取得しました。後で示す詳細の一部をより簡潔にするために、実行はDOP2に制限されています。
推定実行計画は次のとおりです(クリックして拡大):
クエリオプティマイザは、バッチ用にコンパイルされた単一のプランを生成します。バッチ内の各ステートメントは、適格性と推定コストに応じて、シリアル実行またはパラレル実行のマークが付けられます。
並行計画には交換が含まれます (並列処理演算子)。交換はストリームの配布に表示される場合があります 、再パーティションストリーム 、またはストリームを収集 形。これらの交換タイプはそれぞれ、同じ基本コンポーネントを使用しますが、入力と出力の数が異なるだけで、配線が異なります。行モードの並列実行の背景については、並列実行計画–ブランチとスレッドを参照してください。
DOPダウングレード
並列計画の並列度(DOP)は、必要に応じて実行時にダウングレードできます。並列クエリは、DOP 8の要求から開始する場合がありますが、その時点でシステムリソースが不足しているため、徐々にDOP 4、DOP 2、最後にDOP1にダウングレードされます。それが実際に行われているのを見たい場合は、ErikDarlingによるこの短いビデオをご覧ください。
単一スレッドでの並列プランの実行は、キャッシュされた並列プランが、環境設定(アフィニティマスクやリソースガバナーなど)によってDOP1に制限されているセッションによって再利用される場合にも発生する可能性があります。詳細については、神話:SQLServerがすべての並列プランでシリアルプランをキャッシュするを参照してください。
原因が何であれ、キャッシュされた並列プランのDOPダウングレードはありません その結果、新しいシリアルプランがコンパイルされます。 SQL Serverは、無効化することにより、既存の並列プランを再利用します。 交換。その結果、単一のスレッドで実行される「並列」プランが作成されます。交換は引き続きプランに表示されますが、実行時にバイパスされます。
SQL Serverは、実行時に交換を追加することによって、シリアルプランを並列実行に昇格させることはできません。それには、新たなコンパイルが必要になります。
並列プランには、追加のワーカースレッドを利用するために必要なすべての交換が含まれますが、追加のセットアップ作業があります。 並列実行を開始する前に実行時に必要です。明らかな例の1つは、プラン内の特定のタスクに追加のワーカースレッドを割り当てる必要があることですが、それだけではありません。
並列プランがプランキャッシュから取得された時点から始めます。この時点で、現在の要求を処理している元のスレッドのみが存在します。このスレッドは、並列プランでは「コーディネータースレッド」と呼ばれることもありますが、私は「親タスク」という用語を好みます。 」または「親労働者」。それ以外の点では、このスレッドについて特別なことは何もありません。これは、接続がクライアント要求を処理し、シリアルプランを実行して完了するために使用するのと同じスレッドです。
単一のスレッドのみが存在するという点を強調するため 今のところ、この時点での計画を次のように視覚化してほしい:
この投稿では、Sentry One Plan Explorerのスクリーンショットをほぼ独占的に使用しますが、この最初のビューについてのみ、SSMSバージョンも表示します。
どちらの表現でも、主な違いは、交換がまだ存在している場合でも、各演算子に並列処理アイコンがないことです。現在、元の接続スレッドで実行されている親タスクのみが存在します。 追加のワーカースレッドはありません まだタスクが予約、作成、または割り当てられています。上記の計画の表現を念頭に置いて進めてください。
この時点での計画は、基本的には単なるテンプレートです。 これは、将来の実行の基礎として使用できます。特定の実行の準備をするために、SQL Serverは、現在のユーザー、トランザクションコンテキスト、パラメーター値、実行時に作成されたオブジェクト(一時テーブルや変数など)のIDなどの実行時の値を入力する必要があります。
>並列計画の場合、SQL Serverは、内部機構を実行を開始できるポイントに到達させるために、かなりの追加の準備作業を行う必要があります。親タスクのワーカースレッドは、このほとんどすべての作業(および、パート1で取り上げるすべての作業)を実行する責任があります。
特定の実行用にプランテンプレートを変換するプロセスは、実行可能プランの作成と呼ばれます。 。用語が過負荷になり、誤って適用されることが多いため(Microsoftでも)、用語を正確に保つことが難しい場合がありますが、可能な限り一貫性を保つように最善を尽くします。
実行コンテキストについて考えることができます 特定のスレッドに必要なすべての特定のランタイム情報が入力されたプランテンプレートとして。 実行可能プラン シリアルの場合 ステートメントは単一の実行コンテキストで構成され、単一のスレッドがプラン全体を実行します。
パラレル 実行可能プランには、実行コンテキストのコレクションが含まれています :親タスク用に1つ、各並列ブランチのスレッドごとに1つ。追加の並列ワーカースレッドはそれぞれ、独自の実行コンテキスト内で全体的な計画の一部を実行します。たとえば、DOP 8で実行されている3つのブランチを持つ並列プランには、(1 +(3 * 8))=25の実行コンテキストがあります。シリアル実行コンテキストは再利用のためにキャッシュされますが、追加の並列実行コンテキストはキャッシュされません。
親タスクは常に追加の並列タスクの前に存在するため、実行コンテキストゼロが割り当てられます。 。並列ワーカーによって使用される実行コンテキストは、親コンテキストが完全に初期化された後、後で作成されます。追加のコンテキストはクローン化されます 親コンテキストから、特定のタスクに合わせてカスタマイズします(これについてはパート2で説明します)。
実行コンテキストゼロの起動には、いくつかのアクティビティが関係しています。それらすべてをリストすることは実用的ではありませんが、テストクエリに適用できる主なもののいくつかをカバーすることは有用です。 1つのリストにはまだ多すぎるので、それらを(やや恣意的な)セクションに分割します:
1。親コンテキストの初期化
実行のためにステートメントを送信すると、親タスクのコンテキスト(実行コンテキストゼロ)は次のように初期化されます:
- 基本トランザクションへの参照 (明示的、暗黙的、または自動コミット)。並列ワーカーはサブトランザクションを実行しますが、それらはすべて基本トランザクション内でスコープされます。
- ステートメントのパラメータのリスト およびそれらの現在の値。
- プライマリメモリオブジェクト (PMO)メモリの付与と割り当てを管理するために使用されます。
- リンクされたマップ 実行可能プラン内の演算子(クエリノード)の数。
- 必要な大きなオブジェクトのファクトリ (blob)ハンドル。
- 実行中に一定期間保持された複数のロックを追跡するためのロッククラス。すべてのプランでロッククラスが必要なわけではありません 完全にストリーミングするオペレーターは通常、個々の行を順番にロックおよびロック解除するためです。
- 推定されるメモリ付与 クエリ用。
- 行モードのメモリ付与フィードバック 各演算子の構造(SQL Server 2019)。
これらのものの多くは、後で並列タスクによって使用または参照されるため、最初に親スコープに存在する必要があります。
2。親コンテキストメタデータ
実行される次の主なタスクは次のとおりです。
- 推定クエリコストを確認する クエリガバナーのコスト制限設定オプションで設定された制限内です。
- インデックスの使用法を更新する レコード–
sys.dm_db_index_usage_stats
を通じて公開 。 - キャッシュされた式の値(実行時定数)を作成します。
- プロファイリングオペレーターのリストの作成 現在の実行で要求された場合に、行数やタイミングなどの実行時メトリックを収集するために使用されます。プロファイラー自体はまだ作成されておらず、リストのみが作成されています。
- 待機のスナップショットを撮る
sys.dm_exec_session_wait_stats
を介して公開されたセッション待機機能の場合 。
3。 DOPとメモリの付与
現在の親タスクコンテキスト:
- 実行時の並列度を計算します( DOP )。これは、フリーワーカーの数(前述の「DOPダウングレード」を参照)、ノード間に配置できる場所、およびトレースフラグの数の影響を受けます。
- 必要な数のスレッドを予約します。このステップは純粋なアカウンティングです。スレッド自体はこの時点では存在しない可能性があります。 SQL Serverは、許可されているスレッドの最大数を追跡します。 スレッドの予約 その数から減算します。スレッドが終了すると、最大数が再び増加します。
- メモリ付与タイムアウトを設定 。
- 交換バッファに必要なメモリを含む、メモリ許可を計算します。
- 適切なリソースセマフォを介してメモリ付与を取得します 。
- 並列のサブプロセスを処理するためのマネージャーオブジェクトを作成します 。親タスクは最上位のプロセスです。追加のタスクは、サブプロセスとも呼ばれます。 。
この時点でスレッドは「予約」されていますが、SQLServerでTHREADPOOL
が発生する可能性があります。 後で「予約済み」スレッドの1つを使用しようとするときに待機します。この予約により、SQL Serverは常に構成された最大スレッド数にとどまることが保証されますが、物理スレッドがスレッドプールからすぐに利用できるとは限りません。 。その場合、オペレーティングシステムで新しいスレッドを開始する必要がありますが、これには少し時間がかかる場合があります。詳細については、JoshDarnellによる異常なスレッドプール待機を参照してください。
4。クエリスキャンの設定
行モードプランは反復で実行されます ファッション、ルートから始まります。現在の計画では、このモードの実行はまだできていません。すでにかなりの量の実行固有の情報が含まれている場合でも、それは依然として大部分がテンプレートです。
SQL Serverは、現在の構造をイテレータのツリーに変換する必要があります 、それぞれOpen
のようなメソッドを使用します 、GetRow
、およびClose
。イテレータメソッドは、関数ポインタを介して子に接続されています。たとえば、GetRow
を呼び出します ルート上で再帰的にGetRow
を呼び出します リーフレベルに到達し、行がツリーを「バブルアップ」し始めるまで、子オペレーターで。詳細の復習については、イテレータ、クエリプラン、およびそれらが逆方向に実行される理由を参照してください。
親タスクの実行コンテキストの設定は順調に進んでいます。パート2では、SQLServerが反復実行に必要なクエリスキャンツリーを構築する方法について説明します。