はじめに
SQL Serverの製品ドキュメントは、行の目標のトピックについて少し説明しています。 。主な公式リファレンスは次のとおりです。
- ヒント(Transact-SQL)–クエリ(
FAST
およびDISABLE_OPTIMIZER_ROWGOAL
ヒント) - DBCC TRACEON –トレースフラグ(Transact-SQL)(トレースフラグ4138)
- クエリオプティマイザがTop演算子(KB 2667211)を使用している場合、クエリの実行に時間がかかることがあります
そこに含まれているよりも多くの情報を求められる場合、私は通常、次の1つ以上を参照します。
- SQLServerクエリ最適化チームによる実行中の行の目標
- 行の目標の再検討–SQLServerクエリ最適化チームによるFASTヒントガイダンス
- Row Goals Gone Rogue by Bart Duncan
- オプティマイザーの内部:私による詳細な行の目標
- RobFarleyが見逃しているSSISチューニングのヒント
簡単に要約すると、行ゴール機能を使用すると、オプティマイザーは、特定の数の行をすばやく返すことを目的として、実行プラン(または実行プランの一部)を生成できます。これは、完全な潜在的な結果セットに最適化された計画を見つけることを目的とした通常の動作(行の目標なし)とは対照的です。
行の目標戦略とは、一般に、並べ替えやハッシュなどのブロック、セットベースの操作よりも、非ブロックのナビゲーション操作(ネストされたループの結合、インデックスのシーク、ルックアップなど)を優先することを意味します。これは、クライアントが迅速な起動と安定した行のストリームの恩恵を受けることができる場合はいつでも役立ちます(おそらく全体的な実行時間が長くなります-上記のRob Farleyの投稿を参照してください)。より明白で伝統的な使用法もあります。結果を一度に1ページずつ表示します。
当然のことながら、行の目標計画にはリスクの要素が含まれます。オプティマイザーが期待するとおりにすべてが広く実行される場合(利用可能な情報とモデリングの仮定が与えられた場合)、実行プランは、行の目標がない場合よりも迅速かつ効率的に要求された行数のストリーミングを開始します。
残念ながら、行の目標戦略がうまくいかない場合、パフォーマンスが低下する可能性があります(Bart Duncanの投稿を参照)。これは、たとえば、オプティマイザが不完全な情報を持っている場合、好ましくないデータ分散に遭遇した場合、または安全でない仮定を行った場合に発生する可能性があります。いずれにせよ、パフォーマンスの低下の原因は、ほとんどの場合、オプティマイザーが予想するよりもはるかに多くの行を実行時に処理する必要があることです。
行の目標の影響を受ける実行プランの領域を特定すると、理由を理解するのに役立つため、非常に役立ちます。 オプティマイザはそれが行った選択を行いました。これは、行の目標ロジックが不利な結果をもたらす場合に特に重要です。行の目標が果たす役割を理解していないと、オプティマイザーが単に行の数を過小評価しているように見え、人々が根本的な原因を間違った場所(統計など)で探すようになる可能性があります。
行の目標の設定
そもそもどのようなことが行の目標を設定するのかを知っていれば、行の目標の効果を探すのははるかに簡単です。公式ドキュメントでは、キーワード TOP
に関連付けられている行の目標についてよく説明されています。 、 FAST
、 IN
、および EXISTS
。これにより、読者の理解が不完全または誤解を招く可能性があるため、少し時間を取っていくつかの側面を明確にする価値があります。
特定のT-SQLキーワードを使用することを前もって強調したいと思います。 クエリで行の目標が設定されることを保証するものではありません 。公式ドキュメントには、行の目標が可能性がある一般的なシナリオを特定するのに役立つ特定のキーワードが記載されています。 あまり多くの技術を使わずに紹介されます。
覚えておくべき2つ目の一般的なポイントは、行の目標は、目標が通常の見積もりよりも少ない場合にのみ設定されるということです 。結局のところ、全体で50行しか生成されないと予想される場合、100行に最適化されたプランフラグメントを生成する意味はあまりありません。さらに明確にするために、このポイントは、行の目標を設定できるすべての方法に常に適用されます。行の目標を期待しているのに目標が表示されない場合は、これが原因である可能性があります。
最後に、前文については、行の目標はコストベースの最適化であることに注意してください。行の目標はオプティマイザーの選択に影響を与えるため、選択がない場合(つまり、些細な計画)、行の目標の効果はありません。
次に、行の目標を設定できるものを見てみましょう。
高速でトップ
FAST
を使用する クエリヒントは、ルートで行の目標を設定するための信頼できる方法です。 実行計画の(上記の一般的な例外を条件として)。 SET ROWCOUNT n
ステートメントは、同様の最上位行の目標も設定します( n
の場合) もちろん、適用されるステートメントについてはゼロではありません。
TOP
を書く クエリの句も、多くの場合、行の目標になります。完成した実行プランに物理的なトップオペレーターが含まれている限り、トップオペレーターの下のプランの少なくとも一部が行の目標の影響を受けた可能性があります(ここでも、一般的な条件が適用されます)。
トップ演算子はクエリオプティマイザによって導入されたことに注意してください (クエリ指定の TOP
なし 句)は、行の目標を設定することもできます。次のAdventureWorksクエリに示すように、たとえば単純な行番号でフィルタリングする場合など、これが発生する可能性のあるさまざまな方法があるため、これは重要です。
SELECT THN.RowNum, THN.TransactionID FROM ( SELECT TH.TransactionID, RowNum = ROW_NUMBER() OVER ( ORDER BY TH.TransactionID ASC) FROM Production.TransactionHistory AS TH WHERE TH.ProductID = 400 ) AS THN WHERE THN.RowNum >= 10 AND THN.RowNum < 20 ORDER BY THN.RowNum ASC;
そのクエリの実行プランには、オプティマイザによって追加されたTop演算子が含まれています(処理される行数を20に制限するため):
<!-img src ="https://sqlperformance.com/wp-content/uploads/2018/02/Row-Number-Top.png" alt="行番号トップ"width="598" height ="106 "/>
最上位の行の目標は、実行プランのルートに表示される必要はありません。これが、計画の一部が行目標の対象となる可能性があるのに対し、他の部分は対象とならない理由の1つです。
INおよびEXISTS
ドキュメントにはIN
が記載されています およびEXISTS
これらは半結合を表現する2つの一般的な方法だからです または参加禁止 T-SQLで。半結合または反結合を生成しない方法でいずれかのキーワードを使用すると、生成されません 行の目標を設定します。
T-SQLは、半結合または反結合を直接記述する方法を提供しません(ただし、興味深いことにU-SQLは提供します)。そのため、代わりにこのような間接構文を使用する必要があります(Aaron Bertrandは以前に主要なT-SQLオプションを確認しました)記事)。
例:
-- Using IN SELECT P.ProductID FROM Production.Product AS P WHERE P.ProductID IN ( SELECT TH.ProductID FROM Production.TransactionHistory AS TH ); -- Using EXISTS SELECT P.ProductID FROM Production.Product AS P WHERE EXISTS ( SELECT * FROM Production.TransactionHistory AS TH WHERE TH.ProductID = P.ProductID ); -- Using INTERSECT (also removes duplicates but P.ProductID is a key) SELECT P.ProductID FROM Production.Product AS P INTERSECT SELECT TH.ProductID FROM Production.TransactionHistory AS TH;
3つのフォームはすべて、セミジョインを特徴とするまったく同じ実行プランを生成します。
<!-img title="セミジョイン実行プラン"alt="セミジョイン実行プラン"src="https://sqlperformance.com/wp-content/uploads/2018/02/image4.png" width ="344 "height =" 211">
繰り返しになりますが、T-SQLキーワードではなく、行の目標の観点から重要なのはセミ/アンチ結合です。次のクエリでも、同じ半結合が生成されます DISTINCT
のみを使用した実行プラン および通常のINNERJOIN
:
SELECT DISTINCT P.ProductID FROM Production.Product AS P INNER JOIN Production.TransactionHistory AS TH ON TH.ProductID = P.ProductID;
アンチジョインについても同様の例を簡単に作成できます(ただし、 NOT IN
を使用する場合は注意が必要です。 。
2番目の(あまり一般的ではない)例として、 IN
およびEXISTS
キーワードはIF
でも使用できます 半結合を生成するには:
IF EXISTS (SELECT * FROM Production.TransactionHistory WHERE ProductID = 331) PRINT 'Row found'; IF 331 IN (SELECT ProductID FROM Production.TransactionHistory) PRINT 'Row found'; IF 331 = ANY (SELECT ProductID FROM Production.TransactionHistory) PRINT 'Row found';
上記のすべては、ネストされたループの半結合を特徴とする同じ実行プランを生成します:
<!-img title="IFからのプローブとの半結合"alt="IFからのプローブとの半結合"src="https://sqlperformance.com/wp-content/uploads/2018/02/image9.png" width ="486" height ="263">
セミ/アンチ結合の表現方法がどちらであっても、 TOP
よりも可能性は低くなります。 またはFAST
行の目標になります。これがどのように機能するか、およびこれらのタイプの結合が行ゴールロジックをアクティブ化できる理由については、別の投稿で説明します。
行の目標の特定
私の経験では、実行計画で行の目標の影響を見逃すことがよくあります。
SQL Server 2017 CU3(更新:2016SP2および2014SP3に移植された)以前から、文書化された方法はありませんでしたのも不思議ではありません。 実行計画の行目標に関する情報を確認するために!その更新には次のものが含まれます:
- SQL Server 2014、2016、および2017で追加されたクエリ実行プランのオプティマイザー行の目標情報
この機能拡張により、 EstimateRowsWithoutRowGoalが追加されます。 行の目標の影響を受ける各プランオペレーターの属性 。
新しい属性は、通常のすべての場所(DMV、プランキャッシュなど)に表示されますが、SQL Server Management Studioのグラフィカルプラン(SSMSバージョン17.4まで)にはまだ表示されていません。 SQL Serverは新しい属性を送信しますが、SSMSグラフィカルプランは、ローカルバージョンのxmlプランスキーマと一致しないビットを取り除きます。
SSMSのバージョン17.5には、更新されたxmlスキーマとUI要素が付属しており、新しい属性が表示されるようになる予定です。 SentryOneプランエクスプローラーは、情報を取り除く理由がないため、生のxmlビューに新しい属性を既に表示しますが、新しい行の目標情報を他の場所(プラン図など)に組み込むには、更新が必要になります。 )。最初にツールチップに反映されることを期待しています。
上記のSSMSの制限は、UIで推定または実際のグラフィカル実行プランを要求することによって取得されたプランに適用されます。グラフィカルプランの基礎となるxmlを見ると、そうではありません 新しい属性を表示します。
ただし、生のxml showplan出力を個別に要求しても、新しい属性は削除されません。これは、例えばで達成することができます。 SET SHOWPLAN_XML ON
見積もりプランの場合、または SET STATISTICS XML ON
実際の計画のために。いずれの場合も、実際の計画をグラフィカルに表示するSSMSオプションをオフにする必要があります。 SSMSがxmlをインターセプトして削除するのを防ぐため。 showplanスキーマの検証が失敗するため、xmlの結果をクリックすると、グラフィックとしてではなくxmlビューで開きます。
行ゴールの存在を推測する
新しい行の目標計画情報は、将来の更新で以前のSQL Serverバージョンに追加される可能性がありますが、執筆時点ではそれに関する公式ニュースはありません。その間、私たちは推測しようとすることになります。 実行計画で利用可能な他の情報からの行目標の存在。これを試みる方法はたくさんありますが、特に完全で便利な方法はありません。
たとえば、行の目標がアクティブである可能性があります:
- 推定オペレーターコスト 属性が推定I/Oコストの合計よりも小さい および推定CPUコスト 属性に推定実行数を掛けたもの 必要に応じて
- 推定行数 インデックスまたはテーブルスキャンのプロパティ(残余述語なし)がテーブルカーディナリティよりも小さい 財産。無制限スキャンのカーディナリティ推定値は、通常、基になるオブジェクトのカーディナリティと一致すると予想できます。違いがある場合は、行の目標が原因である可能性があります。
- SQL Server 2016 SP1以降のユーザーは、推定行数を比較することで、行目標の存在を推測することもできます。 読み取られる推定行数へのデータアクセス演算子のプロパティ 財産。これは、述語が残っていないデータアクセス演算子にものみ適用されます(ただし、インデックスシークが含まれていると便利です)。
人々はまた、計画のトップオペレーターの下に行の目標が存在すると想定することもあります。これは不完全なアプローチであり、常に正確であるとは限りませんが、単純である(そして何もないよりも優れている)という利点があります。
また、対象のプランを、行ゴール機能が無効になっているときに取得されたプランと比較することもできます(トレースフラグ4138または比較的新しい<code> DISABLE_OPTIMIZER_ROWGOAL を使用) クエリヒント)。これは合理的なアイデアですが、2番目の計画は最初の計画とは非常に異なることが多いため、意味のある比較を行うことはできません。少なくともさらなる調査を促すことができると思います。
文書化されていないトレースフラグオプション
詳細な計画分析が必要な場合、行の目標を確認するための私の頼りになるツールは、長い間、オプティマイザーの出力演算子ツリーに表示される、文書化されていないトレースフラグの組み合わせでした。
例を見てみましょう(これは、目標が通常の見積もりよりも少ない場合にのみ行の目標が設定される方法も示しています)。
次のおもちゃのAdventureWorksクエリには、推定29行(行の目標なし)があります。
SELECT TH.TransactionID FROM Production.TransactionHistory AS TH WHERE TH.Quantity = 100;
実行計画は次のとおりです。
<!-img title="行の目標なしの29行の見積もり"alt="行の目標なしの29行の見積もり"src="https://sqlperformance.com/wp-content/uploads/2018/02/image141.png" width ="250" height ="106">
Quantity
のフィルターに注意してください 残余述語としてスキャンにプッシュされました。 SQL Serverの最新バージョンには、属性 Estimated Number of Rows =29 があります。 および読み取られる推定行=113,443 Clustered Index Scanで、113,443行が処理され、最終的にフィルターを通過する29行が生成されることを示します。
TOP
を使用して28行を要求するようにクエリを変更する 行の目標を設定します:
SELECT TOP (28) TH.TransactionID FROM Production.TransactionHistory AS TH WHERE TH.Quantity = 100 OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607, QUERYTRACEON 8612);
トレースフラグが組み合わされて、次のような出力が生成されます([SSMSメッセージ]タブ):
*** Output Tree: *** PhyOp_Top NoTies [ Card=28 Cost(RowGoal 0,ReW 0,ReB 0,Dist 0,Total 0)= 0.742065 ] PhyOp_Filter [ Card=29 Cost(RowGoal 28,ReW 0,ReB 0,Dist 0,Total 0)= 0.742063 ] PhyOp_Range TBL: Production.TransactionHistory(alias TBL: TH) [ Card=113443 Cost(RowGoal 109531,ReW 0,ReB 0,Dist 0,Total 0)= 0.689488 ] ScaOp_Comp x_cmpEq ScaOp_Identifier QCOL: [TH].Quantity ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=100) ScaOp_Const TI(bigint,Null,ML=8) XVAR(bigint,Not Owned,Value=28) ScaOp_Const TI(bigint,ML=8) XVAR(bigint,Not Owned,Value=0)
その出力は次のことを示しています:
-
TOP(28)
それ自体には行の目標はありません(28行すべてを生成することが期待されているため)。 - フィルター(
Quantity =100
上 )28セットの行目標があります。行の目標がない場合、見積もりは以前と同様に29行になります。 - インデックス範囲スキャンの行目標は109,531です。これは、フィルターの目標である28行を達成するために、オプティマイザーがフィルターに渡す必要があると予想される行数です。行の目標がない場合、インデックス範囲スキャンの推定値は113,443行(テーブルのカーディナリティの合計)です。
調整された行の目標数(および導出された推定コスト)を使用することは、オプティマイザーの選択を非ブロッキングナビゲーション戦略に偏らせるものです。修正された計算は、このおもちゃの例の計画の形を変更しません。これは、28行(29行ではなく)を提供するために利用できるより安価な戦略がないためです。より複雑な(=現実的な)クエリでは、行の目標の効果が非常に大きくなる可能性があります。
SQL Server 2017 CU3で利用可能な新しいプラン情報は、トレースフラグとまったく同じレベルの詳細を提供しません(わかりやすくするために2017 CU3のshowplan出力は省略されています):
<RelOp NodeId="0" PhysicalOp="Top" EstimateRows="28"> <RelOp NodeId="1" PhysicalOp="Clustered Index Scan" EstimateRows="28" EstimateRowsWithoutRowGoal="29" EstimatedRowsRead="113443"> </RelOp> </RelOp>
新しい行のgoal属性に注意してください。残念ながら、残差としてフィルターをスキャンにプッシュすると、スキャン行の目標に関する情報が失われます(109,531)。これにより、 EstimatedRowsReadに少し誤解を招く情報が残ります。 属性。おそらく、将来のアップデートでこの問題に対処する予定です(おそらく、 EstimatedRowsReadWithoutRowGoal を追加することによって) 属性!)誰が知っているか。
それまでの間、文書化されていない別のトレースフラグ(9130)を使用して、純粋に計画診断の目的で、フィルターが残余述語としてスキャンにプッシュされるのを防ぐことができます。その場合、ショープラン情報は次のようになります。
<RelOp NodeId="0" PhysicalOp="Top" EstimateRows="28"> <RelOp NodeId="1" PhysicalOp="Filter" EstimateRows="28" EstimateRowsWithoutRowGoal="29"> <RelOp NodeId="2" PhysicalOp="Clustered Index Scan" EstimateRows="109531" EstimateRowsWithoutRowGoal="113443" EstimatedRowsRead="113443"> </RelOp> </RelOp> </RelOp>
これには、文書化されていないトレースフラグの組み合わせと同じレベルの情報が含まれるようになりました。推定されるグラフィカルプラン(TF 9130を使用)は次のとおりです。
<!-img title ="TF9130を有効にした行の目標計画"alt="TF9130を有効にした行の目標"src="https://sqlperformance.com/wp-content/uploads/2018/02/image19.png "width =" 409 "height =" 104 ">
TOP(29)
で同じクエリを実行する 目標が通常の推定値以上の場合、行の目標がないことを示します:
SELECT TOP (29) TH.TransactionID FROM Production.TransactionHistory AS TH WHERE TH.Quantity = 100 OPTION (QUERYTRACEON 3604, QUERYTRACEON 8607, QUERYTRACEON 8612, QUERYTRACEON 9130);
メッセージタブの出力は次のとおりです。
*** Output Tree: *** PhyOp_Top NoTies [ Card=29 Cost(RowGoal 0,ReW 0,ReB 0,Dist 0,Total 0)= 0.768451 ] PhyOp_Filter [ Card=29 Cost(RowGoal 0,ReW 0,ReB 0,Dist 0,Total 0)= 0.768448 ] PhyOp_Range TBL: Production.TransactionHistory(alias TBL: TH) [ Card=113443 Cost(RowGoal 0,ReW 0,ReB 0,Dist 0,Total 0)= 0.713995 ] ScaOp_Comp x_cmpEq ScaOp_Identifier QCOL: [TH].Quantity ScaOp_Const TI(int,ML=4) XVAR(int,Not Owned,Value=100) ScaOp_Const TI(bigint,Null,ML=8) XVAR(bigint,Not Owned,Value=29) ScaOp_Const TI(bigint,ML=8) XVAR(bigint,Not Owned,Value=0)
すべてのRowGoal 属性がゼロに設定され、行の目標がないことを示します。 SQL Server 2017CU3のshowplanxmlには次のものが含まれています。
<RelOp NodeId="0" PhysicalOp="Top" EstimateRows="29"> <RelOp NodeId="1" PhysicalOp="Filter" EstimateRows="29"> <RelOp NodeId="2" PhysicalOp="Clustered Index Scan" EstimateRows="113443" EstimatedRowsRead="113443"> </RelOp> </RelOp> </RelOp>
EstimateRowsWithoutRowGoalがない ここでの属性は、行の目標が設定されていないことを示しています。
まとめと最終的な考え
実行プランがプランのさまざまな領域の1つ以上の行の目標の影響を受けるには多くの方法があります。行の目標効果が有益な場合があり、要求された数の行を低遅延と高効率で提供します。他の場合には、行の目標の影響がパフォーマンスの問題につながる可能性があります。
T-SQLクエリテキストを見ただけでは、行の目標がオプティマイザのプランの選択に影響を与えたかどうかを常に判断できるとは限りません。考えられる行の目標の原因について最もよく理解されていないのは、おそらくセミジョインまたはアンチジョインの存在です。これについては、後で詳しく説明します。
SQL Server 2017 CU3より前は、少なくとも文書化されていないトレースフラグに頼ることなく(そして出力を一致させて解釈する方法を知っていなければ)、行の目標を検出するための本当に信頼できる方法はありませんでした。新しいshowplan属性は、十分に新しいバージョンを実行しているユーザーにとって非常に便利です。クライアントツールが更新されて表示されると、さらに便利になります。うまくいけば、この拡張機能は古いリリースでも利用できるようになります。