この結合カーディナリティの推定値が非常に大きいのはなぜですか?


18

私は、次のクエリのカーディナリティの推定値が非常に高いと思うことを経験しています:

SELECT dm.PRIMARY_ID
FROM
(
    SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

推定計画はこちらです。テーブルの統計コピーを作成しているので、実際の計画を含めることはできません。ただし、この問題にはあまり関係ないと思います。

SQL Serverは、「dm」派生テーブルから481577行が返されると推定しています。次に、X_LAST_TABLEへの結合が実行された後、4528030000行が返されると推定されますが、JOIN_IDはX_LAST_TIMEの主キーです。0〜481577行の間の結合カーディナリティの推定値が予想されます。代わりに、行の推定値は、外側のテーブルと内側のテーブルをクロス結合するときに取得する行数の10%になります。この計算は、四捨五入で計算されます:481577 * 94025 * 0.1 = 45280277425は4528030000に丸められます。

私は主にこの動作の根本原因を探しています。単純な回避策にも興味がありますが、データモデルの変更や一時テーブルの使用を提案しないでください。このクエリは、ビュー内のロジックを簡略化したものです。いくつかの列でCOALESCEを実行し、それらに参加するのは良い習慣ではないことを知っています。この質問の目標の一部は、データモデルの再設計を推奨する必要があるかどうかを判断することです。

従来のカーディナリティ推定器を有効にしてMicrosoft SQL Server 2014でテストしています。TF 4199などがオンになっています。最終的に関連性がある場合は、トレースフラグの完全なリストを提供できます。

最も関連性の高いテーブル定義は次のとおりです。

CREATE TABLE X_LAST_TABLE (
JOIN_ID NUMERIC(18, 0) NOT NULL
    CONSTRAINT PK_X_LAST_TABLE PRIMARY KEY CLUSTERED (JOIN_ID ASC)
);

また、サーバーの1つで問題を再現したい場合は、統計とともにテーブル作成スクリプトをすべてスクリプト化しました。

いくつかの観察結果を追加するには、TF 2312を使用して推定値を修正しますが、それは私にとっては選択肢ではありません。TF 2301は推定値を修正しません。テーブルの1つを削除すると、推定が修正されます。奇妙なことに、X_DETAIL_LINKの結合順序を変更すると、推定も修正されます。結合順序を変更するということは、クエリを書き換えて、結合順序にヒントを強制しないことを意味します。以下は、結合の順序を変更するだけの場合の推定クエリプランです。


PSあなたはにどのような方法スイッチにすることができた場合bigintの代わりに、decimal(18, 0)あなたは利益を得るでしょう:1)使用8は、すべての値、および2のための代わりの9バイト)意味を持つ可能性がある、代わりにパックされたデータ型のバイト比較可能なデータ型を使用します値を比較するときのCPU時間。
エリック

@ErikE先端をありがとう、しかし私はすでにそれを知っていました。残念ながら、レガシーの理由により、BIGINTではなくNUMERIC(18,0)に固執しています。
ジョーオブビッシュ

一撃の価値があった!
エリック

がnullでない場合、テーブルX_DETAIL2X_DETAIL3テーブルは必要ですか?JOIN_IDX_DETAIL1
エリック

@ErikEこれはMCVEであるため、この時点ではクエリは正確に意味をなしません。
ジョーオブビッシュ

回答:


14

COALESCEいくつかの列でそれらを結合することは良い習慣ではないことを知っています。

スキーマが3NF +(キーおよび制約付き)で、クエリがリレーショナルであり、主にSPJG(selection-projection-join-group by)である場合、適切なカーディナリティおよび分布推定値を生成するのは十分困難です。CEモデルは、これらの原則に基づいて構築されています。より多くの珍しいまたは非リレーショナルクエリである特徴を、より近い1はカーディナリティと選択のフレームワークが扱うことができるものの境界になります。行き過ぎて、CEはあきらめて推測しますます。

MCVEの例の大部分は単純なSPJ(Gなし)です。ただし、単純な内部の等価結合(または半結合)ではなく、主に外部の等価結合(内部結合と反半結合としてモデル化)があります。すべてのリレーションにはキーがありますが、外部キーやその他の制約はありません。結合の1つを除くすべてが1対多であり、これは良いことです。

例外はある多対多の外側の間に参加X_DETAIL_1してX_DETAIL_LINK。MCVEでのこの結合の唯一の機能は、で潜在的に行を複製することX_DETAIL_1です。これは珍しいです種類のものです。

単純な等式述部(選択)とスカラー演算子も優れています。たとえば、属性比較と等しい属性/定数は、通常、モデルで適切に機能します。このような述語の適用を反映するために、ヒストグラムと頻度統計を修正するのは比較的「簡単」です。

COALESCEは、に基づいて構築されますCASE。これは、次のように内部的に実装されますIIF(これはIIF、Transact-SQL言語に登場するずっと前から真実でした)。CE IIFは、UNION相互に排他的な2つの子を持つモデルを作成し、それぞれが入力関係の選択に関するプロジェクトで構成されます。リストされている各コンポーネントにはモデルがサポートされているため、それらを組み合わせるのは比較的簡単です。それでも、抽象化が1層になると、最終結果の精度が低下する傾向があります。これは、より大きな実行計画の安定性と信頼性が低下する理由です。

ISNULL、一方で、エンジンに固有のものです。これ以上基本的なコンポーネントを使用して構築されることはありません。ISNULLたとえば、ヒストグラムへの効果の適用は、NULL値のステップを置き換える(および必要に応じて圧縮する)のと同じくらい簡単です。スカラー演算子が行くように、それはまだ比較的不透明であり、可能な限り避けるのが最善です。それにも関わらず、一般的に言うと、CASEベースの代替よりもオプティマイザーにやさしい(オプティマイザーにやさしくない)。

CE(70および120+)は、SQL Server標準でも非常に複雑です。単純なロジック(秘密の式を使用)を各演算子に適用する場合ではありません。CEはキーと機能の依存関係を知っています。頻度、多変量統計、ヒストグラムを使用して推定する方法を知っています。そして、特別なケース、改良、チェックとバランス、およびサポート構造の絶対的なトンがあります。多くの場合、たとえば複数の方法で結合(頻度、ヒストグラム)を推定し、2つの違いに基づいて結果または調整を決定します。

最後に、基本的なことを1つ説明します。最初のカーディナリティの推定は、クエリツリー内のすべての操作に対して、ボトムアップで実行されます。選択性とカーディナリティーは、最初にリーフ演算子から派生します(基本関係)。修正されたヒストグラムと密度/頻度情報は、親演算子に対して導出されます。ツリーが上に行くほど、エラーが蓄積する傾向があるため、推定の品質が低下する傾向があります。

この単一の最初の包括的な推定は出発点を提供し、最終的な実行計画を考慮する前に発生します(些細な計画のコンパイル段階よりも前に行われます)。この時点でのクエリツリーは、クエリの記述形式をかなり密接に反映する傾向があります(ただし、サブクエリが削除され、簡略化が適用されるなど)。

SQL Serverは、初期推定の直後に、ヒューリスティックな結合の並べ替えを実行します。これは、大まかに言ってツリーを並べ替えて、より小さなテーブルと高選択性の結合を最初に配置しようとします。また、外部結合とクロス積の前に内部結合を配置しようとします。その機能は広範囲ではありません。その努力は完全ではありません。物理的なコストは考慮されません(まだ存在しないため、統計情報とメタデータ情報のみが存在するため)。ヒューリスティックな順序変更は、単純な内部等価結合ツリーで最も効果的です。コストベースの最適化の「より良い」出発点を提供するために存在します。

この結合カーディナリティの推定値が非常に大きいのはなぜですか?

MCVEには、「冗長な」主に冗長な多対多の結合がありCOALESCE、述語には等結合があります。また、演算子ツリーには、内部結合lastがあります。これは、ヒューリスティック結合再順序付けにより、ツリーをより望ましい位置に移動できませんでした。すべてのスカラーと投影を除いて、結合ツリーは次のとおりです。

LogOp_Join [ Card=4.52803e+009 ]
    LogOp_LeftOuterJoin [ Card=481577 ]
        LogOp_LeftOuterJoin [ Card=481577 ]
            LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
                LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
                LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
            LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
        LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
    LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]

不完全な最終推定値がすでに設定されていることに注意してください。これはCard=4.52803e+009、倍精度浮動小数点値4.5280277425e + 9(10進数で4528027742.5)として印刷され、内部に格納されます。

元のクエリの派生テーブルが削除され、投影が正規化されました。最初のカーディナリティおよび選択性の推定が実行されたツリーのSQL表現は次のとおりです。

SELECT 
    PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1
    ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk 
    ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2 
    ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3 
    ON dt.ID = d3.ID
INNER JOIN X_LAST_TABLE lst 
    ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)

(余談ですが、繰り返しCOALESCEは最終計画にも存在します。1回は最終のCompute Scalarに、もう1回は内部結合の内側にあります)。

最終的な結合に注目してください。この内部結合は、(定義により)のデカルト積でX_LAST_TABLEあり、選択(結合述語)がlst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)適用された前の結合出力です。デカルト積の基数は、単純に481577 * 94025 = 45280277425です。

そのために、述部の選択性を決定して適用する必要があります。不透明な拡張COALESCEツリー(UNIONおよびIIF、覚えている)の組み合わせとキー情報への影響、派生したヒストグラム、および以前の「異常な」ほとんど冗長な多対多外部結合の頻度は、CEができないことを意味します。通常の方法で受け入れ可能な推定値を導き出します。

その結果、推測ロジックに入ります。推測ロジックは適度に複雑で、「教育された」推測と「それほど教育されていない」推測アルゴリズムのレイヤーが試行されました。推測のより良い根拠が見つからない場合、モデルは最後の手段の推測を使用します。これは、等価比較の場合:sqllang!x_Selectivity_Equal=固定0.1選択性(10%推測):

呼び出し履歴

-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal

結果は、前述のように、デカルト積で0.1の選択性:481577 * 94025 * 0.1 = 4528027742.5(〜4.52803e + 009)です。

書き直し

問題のある結合がコメント化されると、固定選択性の「最後の手段の推測」が回避されるため、より適切な推定値が生成されます(キー情報は1-M結合によって保持されます)。COALESCE結合述部はCEフレンドリではないため、推定の品質は依然として低い信頼性です。修正された推定値は、少なくとも人間にとってより合理的に見えると思います。

X_DETAIL_LINK 最後配置された外部結合を使用してクエリが記述されている場合、ヒューリスティックな並べ替えは、それを最終的な内部結合と交換できX_LAST_TABLEます。インナーは右隣に参加置く問題の大部分は冗長「珍しい」多対多外の効果が来た結合するので、外部結合することは、早期の再注文の限られた能力に、最終的な推定値を改善する機会を与えた後、トリッキーな選択評価のためにCOALESCE。繰り返しますが、推定値は固定された推測よりも少し良く、おそらく法廷での断固たる反対尋問に耐えられないでしょう。

内部結合と外部結合の混合を並べ替えるのは難しく、時間がかかります(ステージ2の完全な最適化でさえ、理論的な動きの限られたサブセットのみを試みます)。

ISNULLMax Vernonの答えで入れ子になった提案は、救済の固定推測を避けることができますが、最終的な推定はありえないゼロ行です(品位のために1行に引き上げられます)。これは、計算に含まれるすべての統計的基礎について、1行の固定推測である可能性もあります。

0〜481577行の間の結合カーディナリティの推定値が予想されます。

物理的に異なるが、論理的および意味的に同一のサブツリーで異なる時期にカーディナリティの推定が発生する可能性があることを受け入れたとしても、これは合理的な期待です-最終計画は、ベスト(メモグループごと)。計画全体の一貫性の保証がないからといって、個々の参加者が敬意を払うことができるということにはなりません。

一方、最後の手段であると推測された場合、希望はすでに失われているので、なぜ気にするのでしょう。私たちは知っていたすべてのトリックを試し、あきらめました。それ以外の場合、最終的な予想は、このクエリのコンパイルおよび最適化中にすべてがCE内でうまくいかなかったという素晴らしい警告サインです。

MCVEを試してみたところ、120 + CE ISNULLは元のクエリに対してゼロ(= 1)行の最終的な見積もり(ネストされたのような)を生成しましたが、これは私の考え方では受け入れられません。

実際のソリューションには、おそらく、COALESCEまたはのない単純な等結合を可能にするための設計変更が必要ISNULLです。理想的には、クエリのコンパイルに役立つ外部キーおよびその他の制約があります。


10

私は、参加しCompute ScalarCOALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)ことから生じるオペレーターX_LAST_TABLE.JOIN_IDが問題の根本原因であると信じています。歴史的に、コンピューティングスカラーを正確にコストに困難であった12

正確な統計情報を含む最小限の検証可能な例(ありがとう!)を提供しているため、クエリを書き換えて、結合に拡張されるCASE機能が不要になり、COALESCE行の推定がより正確になり、明らかに正確な全体原価計算最後の補遺をご覧ください。

SELECT COALESCE(dm.d1ID, dm.d2ID, dm.d3ID)
FROM
(
    SELECT d1ID = d1.JOIN_ID
        , d2ID = d2.JOIN_ID
        , d3ID = d3.JOIN_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst 
    ON (dm.d1ID IS NOT NULL AND dm.d1ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NOT NULL AND dm.d2ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NULL AND dm.d3ID IS NOT NULL AND dm.d3ID = lst.JOIN_ID);

xID IS NOT NULL技術的には必須ではありませんが、はID = JOIN_IDnull値で結合しないため、意図をより明確に描写するため、それらを含めました。

計画1および計画2

計画1:

ここに画像の説明を入力してください

計画2:

ここに画像の説明を入力してください

新しいクエリには、並列化の利点(?)があります。また、新しいクエリの行の推定出力数は1であり、実際には1日の終わりには、元のクエリの推定4528030000よりも悪い場合があります。新しいクエリの選択演算子のサブツリーコストは243210になりますが、元のクロックは536.535になりますが、これは明らかに少ないです。そうは言っても、最初の見積もりは現実に近いとは思いません。


補遺1。

@Lamakとの議論に拍車をかけられたThe Heap™のさまざまな人々とのさらなる協議の後、上記の私の観測クエリは、並列処理であってもひどく実行されるようです。優れたパフォーマンス優れたカーディナリティの推定の両方を可能にするソリューションは、次のようにをに置き換えることCOALESCE(x,y,z)で構成ISNULL(ISNULL(x, y), z)されます。

SELECT dm.PRIMARY_ID
FROM
(
    SELECT ISNULL(ISNULL(d1.JOIN_ID, d2.JOIN_ID), d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

COALESCECASEクエリオプティマイザーによって「隠された」ステートメントに変換されます。そのため、カーディナリティ推定器は、内部に埋め込まれた列の信頼できる統計を発見するのに苦労しますCOALESCEISNULL本質的な関数であるということは、カーディナリティ推定器にとってはるかに「オープン」です。またISNULL、ターゲットがnull不可であることがわかっている場合は、最適化して削除する価値はありません。

ISNULLバリアントの計画は次のようになります。

ここに画像の説明を入力してください

ここに計画バージョンを貼り付けます)。

参考までに、私は上記のグラフィカルなプランを作成するために使用したすばらしいプランエクスプローラーをセントリーワンに提供しています。


-1

結合条件に従って、テーブルは非常に多くの方法で配置できます。つまり、「特定の方法に変更する」ことで結果が修正されます。

1つのテーブルのみを結合すると正しい結果が得られるとします。

SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID

ここでは、の代わりにまたはX_DETAIL_1を使用できます。X_DETAIL_2X_DETAIL_3

したがって、休息2の表の目的は明確ではありません。

テーブルX_DETAIL_1をさらに2つの部分に分割したようです。

ほとんどの場合、「これらのテーブルにデータを入力するときにエラーが発生します。理想的」X_DETAIL_1X_DETAIL_2およびX_DETAIL_3行の同じ量が含まれている必要があります。

ただし、1つ以上のテーブルに不要な数の行が含まれています。

私が間違っていたらごめんなさい。

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