SQL Serverが統計と矛盾していることが非常に簡単に証明できる見積もりを思い付く理由を理解するのに苦労しています。
一貫性
一貫性の一般的な保証はありません。推定は、異なる統計手法を使用して、異なる時間に異なる(ただし論理的には同等の)サブツリーで計算できます。
これら2つの同一のサブツリーを結合するとクロス積が生成されるはずであるという論理には何の問題もありませんが、推論の選択が他のどの製品よりも健全であると言うことも同様にありません。
初期推定
特定のケースでは、結合の初期カーディナリティ推定は、2つの同一のサブツリーで実行されません。そのときの木の形は次のとおりです。
LogOp_Join
LogOp_GbAgg
LogOp_LeftOuterJoin
LogOp_Get TBL:ar
LogOp_Select
LogOp_Get TBL:tcr
ScaOp_Comp x_cmpEq
ScaOp_Identifier [tcr] .rId
ScaOp_Const値= 508
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .fId
ScaOp_Identifier [tcr] .fId
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .bId
ScaOp_Identifier [tcr] .bId
AncOp_PrjList
AncOp_PrjEl Expr1003
ScaOp_AggFunc stopMax
ScaOp_Convert int
ScaOp_Identifier [tcr] .isS
LogOp_Select
LogOp_GbAgg
LogOp_LeftOuterJoin
LogOp_Get TBL:ar
LogOp_Select
LogOp_Get TBL:tcr
ScaOp_Comp x_cmpEq
ScaOp_Identifier [tcr] .rId
ScaOp_Const値= 508
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .fId
ScaOp_Identifier [tcr] .fId
ScaOp_Comp x_cmpEq
ScaOp_Identifier [ar] .bId
ScaOp_Identifier [tcr] .bId
AncOp_PrjList
AncOp_PrjEl Expr1006
ScaOp_AggFunc stopMin
ScaOp_Convert int
ScaOp_Identifier [ar] .isT
AncOp_PrjEl Expr1007
ScaOp_AggFunc stopMax
ScaOp_Convert int
ScaOp_Identifier [tcr] .isS
ScaOp_Comp x_cmpEq
ScaOp_Identifier Expr1006
ScaOp_Const値= 1
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[ar] .fId
ScaOp_Identifier QCOL:[ar] .fId
最初の結合入力には、投影されていない集約が単純化されており、2番目の結合入力には、そのt.isT = 1
下にプッシュされる述部がt.isT
ありMIN(CONVERT(INT, ar.isT))
ます。それにもかかわらず、isT
述部の選択性計算CSelCalcColumnInInterval
はヒストグラムで使用できます。
CSelCalcColumnInInterval
列:COL:Expr1006
列QCOLのロードされたヒストグラム:[ar] .isT、ID 3の統計から
選択性:4.85248e-005
生成された統計コレクション:
CStCollFilter(ID = 11、CARD = 1)
CStCollGroupBy(ID = 10、CARD = 20608)
CStCollOuterJoin(ID = 9、CARD = 20608 x_jtLeftOuter)
CStCollBaseTable(ID = 3、CARD = 20608 TBL:ar)
CStCollFilter(ID = 8、CARD = 1)
CStCollBaseTable(ID = 4、CARD = 28 TBL:tcr)
(正しい)予想は、この述部によって20,608行が1行に削減されることです。
結合推定
問題は、他の結合入力の20,608行がこの1行とどのように一致するかです。
LogOp_Join
CStCollGroupBy(ID = 7、CARD = 20608)
CStCollOuterJoin(ID = 6、CARD = 20608 x_jtLeftOuter)
...
CStCollFilter(ID = 11、CARD = 1)
CStCollGroupBy(ID = 10、CARD = 20608)
...
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL:[ar] .fId
ScaOp_Identifier QCOL:[ar] .fId
一般に結合を推定するには、いくつかの異なる方法があります。たとえば、次のことができます。
- 各サブツリーの各プラン演算子で新しいヒストグラムを導出し、それらを結合で整列させ(必要に応じてステップ値を補間)、それらがどのように一致するかを確認します。または
- ヒストグラムのより単純な「粗い」アライメントを実行します(ステップバイステップではなく、最小値と最大値を使用します)。または
- 結合列のみの個別の選択性を計算し(ベーステーブルから、フィルタリングなし)、非結合述語の選択性効果を追加します。
- ...
使用中のカーディナリティー推定量およびいくつかのヒューリスティックに応じて、それらのいずれか(またはバリエーション)を使用できます。詳細については、Microsoftホワイトペーパー「SQL Server 2014 Cardinality Estimatorによるクエリプランの最適化」を参照してください。
バグ?
さて、質問で述べたように、この場合、「単純な」単一列結合(on fId
)はCSelCalcExpressionComparedToExpression
電卓を使用します。
計算の計画:
CSelCalcExpressionComparedToExpression [ar] .fId x_cmpEq [ar] .fId
列QCOLのヒストグラムをロードしました:ID 2の統計から[ar] .bId
列QCOLのロードされたヒストグラム:ID 1の統計からの[ar] .fId
選択性:0
この計算では、20,608行と1つのフィルター処理された行を結合すると選択性がゼロになると評価されます。一致する行はありません(最終計画で1行として報告されます)。これは間違っていますか?はい、おそらく新しいCEにバグがあります。1つの行がすべての行に一致するか、まったく一致しないと考えることができるので、結果は妥当かもしれませんが、そうでなければ信じる理由があります。
詳細は実際にはかなり複雑ですがfId
、フィルタの選択性によって変更され、20608 * 20608 * 4.85248e-005 = 20608
行を提供する、フィルタ処理されていないヒストグラムに基づく推定値の期待は非常に合理的です。
この計算に従うことは、のCSelCalcSimpleJoinWithDistinctCounts
代わりに計算機を使用することを意味しCSelCalcExpressionComparedToExpression
ます。これを行うための文書化された方法はありませんが、興味がある場合は、文書化されていないトレースフラグ9479を有効にできます。
最終的な結合では、2つの単一行入力から20,608行が生成されますが、これは驚くべきことではありません。これは、TF 9481の下で元のCEによって作成された同じ計画です。
詳細は扱いにくい(そして調査に時間がかかる)と述べましたが、私が知る限り、問題の根本的な原因は、rId = 508
選択性が0の述語に関連しています。このゼロ推定値は、通常の方法で1行に引き上げられます。これは、入力ツリー内の下位述語を考慮に入れると、問題の結合でゼロ選択性推定値に寄与するようです(したがって、の統計をロードしますbId
)。
外部結合が(1行に上げるのではなく)ゼロ行の内側の推定値を保持できるようにする(したがって、すべての外側の行が適格になる)ことで、どちらの計算機でも「バグのない」結合推定値が得られます。これを調べることに興味がある場合、文書化されていないトレースフラグは9473(単独)です。
結合カーディナリティの推定の動作は、CSelCalcExpressionComparedToExpression
別の文書化されていないバリエーションフラグ(9494)で「bId」を考慮しないように変更することもできます。あなたがそのようなことに興味を持っていることを知っているので、これらすべてに言及します。解決策を提供するからではありません。マイクロソフトに問題を報告し、マイクロソフトが問題を解決する(またはしない)まで、クエリを異なる方法で表現することがおそらく最善の方法です。行動が意図的であるかどうかに関係なく、彼らは退行について耳を傾ける必要があります。
最後に、再生スクリプトで言及されているもう1つのことを整理するために、質問計画でのフィルターの最終位置はGbAggAfterJoinSel
、結合出力の上に集約とフィルターを移動するコストベースの探索の結果です。行の数。予想どおり、フィルターは最初は結合の下にありました。