6

次のような2000互換モードで実行されているSQL2005サーバー上のデータでいっぱいのテーブルItemValueがあります(これはユーザー定義値テーブルです)。

ID    ItemCode     FieldID   Value
--    ----------   -------   ------
 1    abc123             1   D
 2    abc123             2   287.23
 4    xyz789             1   A
 5    xyz789             2   3782.23
 6    xyz789             3   23
 7    mno456             1   W
 9    mno456             3   45
                                 ... and so on.

FieldIDItemFieldテーブルから取得されます。

ID   FieldNumber   DataFormatID   Description   ...
--   -----------   ------------   -----------
 1             1              1   Weight class
 2             2              4   Cost
 3             3              3   Another made up description
 .             .              x   xxx
 .             .              x   xxx
 .             .              x   xxx
 x             91  (we have 91 user-defined fields)

2000モードではPIVOTを実行できないため、CASEとGROUP BYを使用して醜いクエリを作成し、一部のレガシーアプリでどのようにデータを取得するかを確認します。

ItemNumber   Field1   Field2    Field3 .... Field51
----------   ------   -------   ------
    abc123   D        287.23    NULL
    xyz789   A        3782.23   23
    mno456   W        NULL      45

このテーブルは、51番目のUDFまでの値を表示するためにのみ必要であることがわかります。クエリは次のとおりです。

SELECT
    iv.ItemNumber,
    ,MAX(CASE WHEN f.FieldNumber = 1 THEN iv.[Value] ELSE NULL END) [Field1]
    ,MAX(CASE WHEN f.FieldNumber = 2 THEN iv.[Value] ELSE NULL END) [Field2]
    ,MAX(CASE WHEN f.FieldNumber = 3 THEN iv.[Value] ELSE NULL END) [Field3]
        ...
    ,MAX(CASE WHEN f.FieldNumber = 51 THEN iv.[Value] ELSE NULL END) [Field51]
FROM ItemField f
LEFT JOIN ItemValue iv ON f.ID = iv.FieldID
WHERE f.FieldNumber <= 51
GROUP BY iv.ItemNumber

FieldNumber制約が<=51の場合、実行計画は次のようになります。

SELECT <== Computer Scalar <== Stream Aggregate <== Sort (Cost: 70%) <== Hash Match <== (Clustered Index Seek && Table Scan)

そしてそれは速いです!約1秒で100,000以上のレコードを取り戻すことができます。これは、私たちのニーズに合っています。

However, if we had more UDFs and I change the constraint to anything above 66 (yes, I tested them one by one) or if I remove it completely, I lose the Sort in the Execution plan, and it gets replaced with a whole bunch of Parallelism blocks that gather, repartition, and distribute streams, and the entire thing is slow (30 seconds for even just 1 record).

FieldNumber has a clustered, unique index, and is part of composite primary key with the ID column (non-clustered index) in the ItemField table. The ItemValue table's ID and ItemNumber columns make a PK, and there is an extra non-clustered index on the ItemNumber column.

What is the reasoning behind this? Why does changing my simple integer constraint change the entire execution plan?

And if you're up to it... what would you do differently? There's a SQL upgrade planned for a couple months from now but I need to get this problem fixed before that.

4

3 に答える 3

5

SQL ServerCHECKクエリを最適化するときに制約を考慮に入れるのに十分賢いです。

が最適化され、オプティマイザf.FieldNumber <= 51は2つのテーブル全体を結合する必要があることを確認します(これは、を使用して行うのが最適ですHASH JOIN)。

制約がない場合、エンジンは条件をチェックする必要があり、おそらくこれを行うためにインデックストラバーサルを使用します。これは遅くなる可能性があります。

クエリの計画全体を投稿していただけますか?SET SHOWPLAN_TEXT ON実行してからクエリを実行するだけです。

アップデート:

この背後にある理由は何ですか?単純な整数制約を変更すると、実行プラン全体が変更されるのはなぜですか?

制約によって条件を意味するWHERE場合、これはおそらく別のことです。

セット操作(これがSQL行うこと)には、単一の最も効率的なアルゴリズムはありません。各アルゴリズムの効率は、セット内のデータ分布に大きく依存します。

たとえば、サブセットを取得する場合(これがWHERE句の機能です)、インデックス内のレコードの範囲を見つけてインデックスレコードポインターを使用してテーブル内のデータ行を見つけるか、テーブル内のすべてのレコードをスキャンしてフィルター処理することができます。条件を使用しWHEREます。

前者の操作の効率はm × const、後者の効率はですn。ここで、mは条件を満たすレコードの数でありn、はテーブル内のレコードの総数const > 1です。

これはm、フルスキャンの値が大きいほど効率的であることを意味します。

SQL Serverはそれを認識し、設定された操作のデータ分散に影響を与える定数に応じて実行プランを変更します。

これを行うには、統計SQL Serverを維持します。インデックスが作成された各列のデータ分布の集計ヒストグラムを維持し、それらを使用してクエリプランを作成します。

したがって、条件の整数を変更するWHEREと、実際には、基になるセットのサイズとデータ分布に影響し、SQL Serverそのサイズとレイアウトのセットで動作するのに最適なアルゴリズムを再検討することになります。

于 2010-03-05T17:20:53.483 に答える
0

多数のParallelismブロックに置き換えられます

これを試して:

SELECT
    iv.ItemNumber,
    ,MAX(CASE WHEN f.FieldNumber = 1 THEN iv.[Value] ELSE NULL END) [Field1]
    ,MAX(CASE WHEN f.FieldNumber = 2 THEN iv.[Value] ELSE NULL END) [Field2]
    ,MAX(CASE WHEN f.FieldNumber = 3 THEN iv.[Value] ELSE NULL END) [Field3]
        ...
    ,MAX(CASE WHEN f.FieldNumber = 51 THEN iv.[Value] ELSE NULL END) [Field51]
FROM ItemField f
LEFT JOIN ItemValue iv ON f.ID = iv.FieldID
WHERE f.FieldNumber <= 51
GROUP BY iv.ItemNumber
OPTION (Maxdop 1)

Option(Maxdop 1)を使用することにより、実行プランの並列処理を防ぐことができます。

于 2010-03-05T17:55:47.060 に答える
0

66で、一方のプランをもう一方のプランよりも使用する方がよいと判断する内部コスト見積もりの​​しきい値に達しています。そのしきい値が何であるか、そしてなぜそれが起こるのかは実際には重要ではありません。WHEREを変更するだけでなく、疑似「ピボット」投影フィールドも変更するため、クエリはFieldNumber値ごとに異なることに注意してください。

これで、テーブルとクエリ、および挿入/更新/削除/パターンのすべての詳細がわかりませんが、特定のクエリについて、ItemValueテーブルの適切なクラスター化インデックス構造を投稿しました。

CREATE CLUSTERED INDEX  [cdxItemValue] ON ItemValue (FieldID, ItemNumber);

この構造により、この「ピボット」クエリの結果を中間ソートする必要がなくなります。

于 2010-03-05T19:12:30.187 に答える