シークがオプティマイザによって選択されない理由
TL:DR拡張計算列定義は、結合を最初に並べ替えるオプティマイザの機能を妨害します。開始点が異なると、コストベースの最適化はオプティマイザを介して異なるパスをたどり、最終的には異なる最終計画の選択になります。
細部
非常に単純なクエリを除いて、オプティマイザは可能なプランのスペース全体を探索しようとはしません。代わりに、合理的な外観の開始点を選択し、合理的な計画が見つかるまで、1つ以上の検索フェーズで、論理的および物理的なバリエーションを調査するために予算内の労力を費やします。
2つのケースで異なる計画(異なる最終コスト見積もり)を取得する主な理由は、開始点が異なるためです。別の場所から開始すると、最適化は別の場所で終わります(探索と実装の反復回数が制限された後)。これがかなり直感的であることを願っています。
出発点私は言及は、いくらかのクエリのテキスト表現に基づいているが、それは、クエリのコンパイルの解析、結合、正規化、および簡略化の段階を通過するときに変更が内部ツリー表現に対して行われます。
重要なことに、正確な開始点は、オプティマイザによって選択された最初の結合順序に大きく依存します。この選択は、統計が読み込まれる前、および基数の推定が導出される前に行われます。ただし、各テーブルの合計カーディナリティ(行数)は既知であり、システムメタデータから取得されています。
したがって、最初の結合順序はヒューリスティックに基づいています。たとえば、オプティマイザは、小さなテーブルが大きなテーブルの前に結合され、内部結合が外部結合(およびクロス結合)の前に来るようにツリーを書き直そうとします。
計算列の存在は、このプロセス、特にオプティマイザが外部結合をクエリツリーにプッシュする機能を妨害します。これは、結合の並べ替えが発生する前に計算列が基になる式に展開されるため、複雑な式を通過して結合を移動する方が、単純な列参照を通過して移動するよりもはるかに難しいためです。
含まれるツリーは非常に大きいですが、説明のために、非計算列の初期クエリツリーは次のように始まります(上部にある2つの外部結合に注意してください)。
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_LeftOuterJoin
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL:dbo.table1(エイリアスTBL:a4)
LogOp_Select
LogOp_Get TBL:dbo.table6(エイリアスTBL:a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a3] .col18
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
LogOp_Select
LogOp_Get TBL:dbo.table1(エイリアスTBL:a1)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a1] .col2
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
LogOp_Select
LogOp_Get TBL:dbo.table5(エイリアスTBL:a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a2] .col2
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a4] .col2
ScaOp_Identifier QCOL:[a3] .col19
LogOp_Select
LogOp_Get TBL:dbo.table7(エイリアスTBL:a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a7] .col22
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a4] .col2
ScaOp_Identifier QCOL:[a7] .col23
LogOp_Select
LogOp_Get TBL:table1(エイリアスTBL:cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[cdc] .col6
ScaOp_Const TI(smallint、ML = 2)XVAR(smallint、Not Owned、Value = 4)
LogOp_Get TBL:dbo.table5(エイリアスTBL:a5)
LogOp_Get TBL:table2(エイリアスTBL:cdt)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a5] .col2
ScaOp_Identifier QCOL:[cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a4] .col2
ScaOp_Identifier QCOL:[cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[cdt] .col1
ScaOp_Identifier QCOL:[cdc] .col1
LogOp_Get TBL:table3(エイリアスTBL:ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[ahcr] .col9
ScaOp_Identifier QCOL:[cdt] .col1
計算列クエリの同じフラグメントは次のとおりです(外部結合がはるかに低く、拡張された計算列定義、および(内部)結合順序の他の微妙な違いに注意してください)。
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL:dbo.table1(エイリアスTBL:a4)
LogOp_Select
LogOp_Get TBL:dbo.table6(エイリアスTBL:a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a3] .col18
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
LogOp_Select
LogOp_Get TBL:dbo.table1(エイリアスTBL:a1
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a1] .col2
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
LogOp_Select
LogOp_Get TBL:dbo.table5(エイリアスTBL:a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a2] .col2
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a4] .col2
ScaOp_Identifier QCOL:[a3] .col19
LogOp_Select
LogOp_Get TBL:dbo.table7(エイリアスTBL:a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a7] .col22
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a4] .col2
ScaOp_Identifier QCOL:[a7] .col23
LogOp_Project
LogOp_LeftOuterJoin
LogOp_Join
LogOp_Select
LogOp_Get TBL:table1(エイリアスTBL:cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[cdc] .col6
ScaOp_Const TI(smallint、ML = 2)XVAR(smallint、Not Owned、Value = 4)
LogOp_Get TBL:table2(エイリアスTBL:cdt)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[cdc] .col1
ScaOp_Identifier QCOL:[cdt] .col1
LogOp_Get TBL:table3(エイリアスTBL:ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[ahcr] .col9
ScaOp_Identifier QCOL:[cdt] .col1
AncOp_PrjList
AncOp_PrjEl QCOL:[cdc] .col7
ScaOp_Convert char collate 53256、Null、Trim、ML = 6
ScaOp_IIF varchar collate 53256、Null、Var、Trim、ML = 6
ScaOp_Comp x_cmpEq
ScaOp_Intrinsic isnumeric
ScaOp_Intrinsic right
ScaOp_Identifier QCOL:[cdc] .col4
ScaOp_Const TI(int、ML = 4)XVAR(int、Not Owned、Value = 4)
ScaOp_Const TI(int、ML = 4)XVAR(int、Not Owned、Value = 0)
ScaOp_Const TI(varchar collate 53256、Var、Trim、ML = 1)XVAR(varchar、Owned、Value = Len、Data =(0、))
ScaOp_Intrinsicサブストリング
ScaOp_Const TI(int、ML = 4)XVAR(int、Not Owned、Value = 6)
ScaOp_Const TI(int、ML = 4)XVAR(int、Not Owned、Value = 1)
ScaOp_Identifier QCOL:[cdc] .col4
LogOp_Get TBL:dbo.table5(エイリアスTBL:a5)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a5] .col2
ScaOp_Identifier QCOL:[cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[a4] .col2
ScaOp_Identifier QCOL:[cdc] .col2
統計が読み込まれ、最初の結合順序が設定された直後にツリーで初期カーディナリティの推定が実行されます。異なる順序で結合を行うと、これらの推定にも影響が及ぶため、後のコストベースの最適化中にノックオン効果があります。
最後に、このセクションでは、外部結合をツリーの途中で動かさないようにすることで、コストベースの最適化中に、結合の並べ替え規則がさらに一致するのを防ぐことができます。
プランガイド(または、USE PLAN
ヒント:クエリの例)を使用すると、提供されたテンプレートの一般的な形状と機能に基づいて、検索戦略がより目標指向のアプローチに変わります。これは、プランガイドまたはヒントが使用されている場合に、オプティマイザがtable1
計算列スキーマと非計算列スキーマの両方に対して同じシーク計画を見つけることができる理由を説明しています。
シークを実現するために何か別のことができるか
これは、オプティマイザが許容可能なパフォーマンス特性を備えたプランをそれ自体で見つけられない場合にのみ心配する必要があるものです。
すべての通常の調整ツールが潜在的に適用可能です。たとえば、クエリをより簡単な部分に分割したり、利用可能なインデックスを確認して改善したり、新しい統計を更新または作成したりできます。
これらすべてのものは、カーディナリティの見積もり、オプティマイザを通過するコードパスに影響を与え、コストベースの決定に微妙な方法で影響を与える可能性があります。
最終的にはヒント(または計画ガイド)を使用することもできますが、それは通常、理想的な解決策ではありません。
コメントからの追加の質問
クエリなどを簡略化するのが最善であることに同意しますが、オプティマイザが最適化を続行して同じ結果に到達する方法(トレースフラグ)はありますか?
いいえ、完全な検索を実行するためのトレースフラグはなく、必要ありません。可能な検索スペースは広大であり、宇宙の時代を超えるコンパイル時間は受け入れられないでしょう。また、オプティマイザは考えられるすべての論理変換を知っているわけではありません(誰も知りません)。
また、列が永続化されているため、複雑な拡張が必要なのはなぜですか?オプティマイザが拡張を避け、通常の列のように扱い、同じ開始点に到達できないのはなぜですか?
計算された列は(ビューのように)拡張され、追加の最適化の機会を可能にします。拡張は、たとえば、プロセスの後半で永続化された列またはインデックスに一致する可能性がありますが、これは最初の結合順序が修正された後に行われます。