web-dev-qa-db-ja.com

このOPTIMIZE FOR UNKNOWNによってクエリが数秒改善されるのはなぜですか?

それでは、SSRSレポートで使用している非ストアドプロシージャクエリを用意しました。このクエリはhellishly遅い(originalバージョンを持っているこのクエリの過去2時間実行中(まだ完了していません)、それを改善するために最初から書き直し、次のことを思いつきました。

これが退屈なWordの問題の部分です。

営業担当者ごとの_TOP 5_クライアントのリストをプルしたいのですが、リストから_TOP 10_合計クライアントをexclude除外します。 (つまり、John DoeにクライアントA、B、C、D、およびEがあり、クライアントCがトップ10の1つである場合、A、B、D、およびEのみをプルします。)

これを行うために、最初のクエリはIN (... NOT IN ( ) )を使用したので、INのネストが問題であると考え、それを書き換えるために、本当にすべてを壊す_OUTER APPLY_を実行しました。

とにかく、すべてを修正してクエリを実行しましたが、パラメーターのスニッフィングであると想定した場合でも、10〜15秒かかりました。調査するために、SSMSでクエリを実行し、OPTION (RECOMPILE)を追加して(どのクエリプランが生成するかを確認するため)、以下を取得しました。

Plan 1

それは見ることができます ここではブレント・オザーの「ペースト・ザ・プラン」 。これを生成したクエリは次のとおりです。

_DECLARE @Top10Temp TABLE (Id INT)
INSERT INTO @Top10Temp
SELECT TOP 10 Id
FROM Object1
WHERE Column2  = @ReportId
  AND Column3  = 0
GROUP BY Id
ORDER BY SUM(Column4 + Column5) DESC

SELECT Object2.*
FROM Object1 AS Object2
OUTER APPLY (
    SELECT TOP 5
        Object3.Id,
        SUM(Object3.Column4 + Object3.Column5) AS Column6
    FROM Object1 AS Object3
    WHERE Object3.Column3 = 0
      AND Object3.Column7 = Object2.Column7
      AND Object3.Column2 = @ReportId
    GROUP BY
        Object3.Id
    ORDER BY
        SUM(Object3.Column4 + Object3.Column5) DESC
) AS Object4
WHERE Object2.Column2 =      @ReportId
  AND Object2.Column3 =      0
  AND Object2.Id      =      Object4.Id
  AND Object2.Id      NOT IN (SELECT Id FROM @Top10Temp)
ORDER BY Object2.Column7
OPTION (RECOMPILE)
_

これで、同じクエリですが、OPTION (OPTIMIZE FOR UNKNOWN)を使用すると、次のプランが生成されます:

Plan 2

「プランの貼り付け」でも確認できます 。このプランは1秒未満で実行されました

_#_を_@ReportId_変数と同じsameとしてOPTION (OPTIMIZE FOR (@ReportId = #))を追加すると、 2番目と同じクエリプラン。

私は何か間違ったことをしましたか?何が起こったのかよくわからないので、情報をいただければ幸いです。 (また、ヒントを介してオプティマイザに影響を与えようとすることは本当に嫌いですが、必要な場合は維持します。)

7
Der Kommissar

「調査するには、SSMSでクエリを実行しました...」それが問題です。ローカル変数は、統計の密度ベクトルを使用して、はるかに優れた行推定を生成します。その結果、すでに不明で最適化されています。パラメータ化された動的SQLはヒストグラムを使用します。ヒストグラムは、特定のセクションの行数全体を引き出します。

各Paste the Planリンクの推定数と実際の行数を確認します。 2番目のリンクは、最初のリンクよりもwaaaayyyyの推定が優れています。

SSRSクエリを開発インスタンスに展開し、パフォーマンスの問題があると思われるため、いくつかのテストを実行します。

できれば、これらの獣のテーブルで統計を更新するか、インデックスを再構築してください。

リンク: 統計ヒストグラムと密度ベクトルの内側

5

遅い計画では、ノード4でのインデックスシークからカーディナリティの推定値が低くなります。推定される行数は1ですが、実際の行数は3261です。シーク述語は次のとおりです。

_Seek Keys[1]: Prefix: Database1.Schema1.Object1.Column2, Database1.Schema1.Object1.Column3 = Scalar Operator(ScalarString7), Scalar Operator(ScalarString2)
_

同じテーブルの2つの異なる列でフィルタリングしています。多くの場合、SQL Serverにはそのシナリオの正確な推定を行うための十分な情報がないため、CEバージョン、パッチ、トレースフラグなどに依存するモデリングの仮定を行います。たとえば、列に相関関係がないと想定し、選択性を乗算します。フィルター間に何らかの相関関係がある場合、その結果、推定値が低くなる可能性があります。

一般的に私はあなたが悪い見積もりで良いパフォーマンスを得るなら、あなたはおそらく幸運になり、あなたの運はある時点でなくなるかもしれないと言います。その見積もりを修正しようと思います。不足している情報が多すぎるため、正確な指示を出すことはできません(IPの問題により、不足している情報の一部を共有することはできません)が、複数列の統計またはインデックスが役立つ可能性があると言えます。一時テーブルへのフィルタリング後にテーブルの主キーを格納することは、常に機能する方法です。より正確な見積もりでは、高速プランに似たクエリプランが表示されると思います。

OPTION (RECOMPILE)ヒントを追加して、何も問題はありませんでした。運が悪かっただけでパフォーマンスが低下した可能性があります。パラメータを埋め込む最適化は通常、問題を引き起こす代わりに役立ちます。 _OPTIMIZE FOR UNKNOWN_を使用すると、SQL Serverは統計オブジェクトを別の方法で使用します。使用すると、実際に近い推定値が得られます。

長期的な解決策として_OPTIMIZE FOR UNKNOWN_は使用しません。変数の値を変更すると問題が発生する可能性がある_@ReportId_の値に応じて、クエリプランは変更されません。また、これは少し間接的な修正であり、そのしくみを理解していないことを認めました。カーディナリティの見積もりを修正するか、中間結果を一時テーブルに戦略的に具体化することにより、問題をより直接的に攻撃する方が良いでしょう。一般に、統計がないため、テーブル変数の使用は避けてください。テーブル変数の使用例は非常に限られているため、他に選択肢がない場合にのみ使用することをお勧めします。

4
Joe Obbish