永続的な計算された列が原因でスキャン


9

通常の列を永続的な計算列に変換すると、このクエリでインデックスシークを実行できなくなります。どうして?

2016 SP1 CU1を含むいくつかのSQL Serverバージョンでテストされています。

レプロス

問題はtable1col7です。

テーブルとクエリは、オリジナルの部分的な(そして簡略化された)バージョンです。クエリが別の方法で書き直される可能性があることは承知しており、何らかの理由で問題を回避しますが、コードに触れないようにする必要がありtable1ます。

ポールホワイトが示したように(ありがとう!)、シークは強制された場合に利用可能になるため、オプティマイザによってシークが選択されない理由と、シークを変更することなく、必要に応じてシークを実行するために別の方法を実行できるかどうかが問題になります。コード?

問題のある部分を明確にするために、不良な実行計画の関連するスキャンを以下に示します。

予定

回答:


12

シークがオプティマイザによって選択されない理由


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 collat​​e 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 collat​​e 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 collat​​e 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 collat​​e 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 collat​​e 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 collat​​e 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 collat​​e 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 collat​​e 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 collat​​e 53256、Null、Trim、ML = 6
                            ScaOp_IIF varchar collat​​e 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 collat​​e 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計算列スキーマと非計算列スキーマの両方に対して同じシーク計画を見つけることができる理由を説明しています。

シークを実現するために何か別のことができるか

これは、オプティマイザが許容可能なパフォーマンス特性を備えたプランをそれ自体で見つけられない場合にのみ心配する必要があるものです。

すべての通常の調整ツールが潜在的に適用可能です。たとえば、クエリをより簡単な部分に分割したり、利用可能なインデックスを確認して改善したり、新しい統計を更新または作成したりできます。

これらすべてのものは、カーディナリティの見積もり、オプティマイザを通過するコードパスに影響を与え、コストベースの決定に微妙な方法で影響を与える可能性があります。

最終的にはヒント(または計画ガイド)を使用することもできますが、それは通常、理想的な解決策ではありません。


コメントからの追加の質問

クエリなどを簡略化するのが最善であることに同意しますが、オプティマイザが最適化を続行して同じ結果に到達する方法(トレースフラグ)はありますか?

いいえ、完全な検索を実行するためのトレースフラグはなく、必要ありません。可能な検索スペースは広大であり、宇宙の時代を超えるコンパイル時間は受け入れられないでしょう。また、オプティマイザは考えられるすべての論理変換を知っているわけではありません(誰も知りません)。

また、列が永続化されているため、複雑な拡張が必要な​​のはなぜですか?オプティマイザが拡張を避け、通常の列のように扱い、同じ開始点に到達できないのはなぜですか?

計算された列は(ビューのように)拡張され、追加の最適化の機会を可能にします。拡張は、たとえば、プロセスの後半で永続化された列またはインデックスに一致する可能性がありますが、これは最初の結合順序が修正された後に行われます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.