トップ1を追加するとパフォーマンスが劇的に低下するのはなぜですか?


39

私はかなり単純なクエリを持っています

SELECT TOP 1 dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

それは私に恐ろしいパフォーマンスを与えています(それが終わるのを待つことを決して気にしないような)クエリプランは次のようになります。

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

しかし、削除するTOP 1と、次のような計画が得られ、1〜2秒で実行されます。

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

以下のPKとインデックスの修正。

TOP 1クエリプランが変更されたからといって驚くことはありませんが、それによって事態がさら​​に悪化していることに少し驚いています。

注:この投稿の結果を読んで、a Row Goalなどの概念を理解しています。私が興味を持っているのは、より良いプランを使用するようにクエリを変更する方法です。現在、データを一時テーブルにダンプしてから、最初の行を取り出しています。より良い方法があるかどうか疑問に思っています。

編集事実の後にこれを読んでいる人々のために、ここにいくつかの追加情報があります。

  • Document_Queue-PK / CIはD_IDであり、〜5k行があります。
  • Correspondence_Journal-PK / CIはFILE_NUMBER、CORRESPONDENCE_IDで、行数は約140万です。

私が始めたとき、他のインデックスはありませんでした。私はCorrespondence_Journal(Document_Id、File_Number)で1つになりました


1
DOCUMENT_ID2つのテーブル間の関係を強制する外部キー制約がありますか(またはすべてのレコードににCORRESPONDENCE_JOURNAL一致するレコードがありDOCUMENT_QUEUEますか)?
ダニエルハットマッハー

回答:


28

ハッシュ結合を強制してみてください*

SELECT TOP 1 
       dc.DOCUMENT_ID,
       dc.COPIES,
       dc.REQUESTOR,
       dc.D_ID,
       cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
INNER HASH JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
       AND dc.QUEUE_DATE <= GETDATE()
       AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

オプティマイザーは、おそらくループがトップ1でより良くなると考えていたので、そのようなことは理にかなっていますが、実際にはここでは機能しませんでした。ここでの推測ではありますが、おそらくそのスプールの推定コストはオフでした-TEMPDBを使用しています-TEMPDBのパフォーマンスが低い可能性があります。


* 結合のヒントには注意してください。これらは、プランテーブルのアクセス順序を、クエリ内のテーブルの記述された順序(OPTION (FORCE ORDER)指定されているかのように)に一致せるためです。ドキュメントのリンクから:

BOLエキス

これは、この例では望ましくない効果を生むことはありませんが、一般的には非常によく起こります。FORCE ORDER(暗黙的または明示的)は、順序の強制を超える非常に強力なヒントです。部分的な集約や並べ替えなど、広範なオプティマイザー手法の適用を防ぎます。

OPTION (HASH JOIN) クエリこれが意味するものではありませんので、ヒントは、適当な場合には控えめかもしれFORCE ORDER。ただし、クエリ内のすべての結合に適用されます。他のソリューションが利用可能です。


1
正解のように見えますが、それと単純な計画の唯一の違いは、前面に追加の並べ替えがあったことです。
ケネスフィッシャー

3
私はこの答えが好きかどうかわかりません。結合のヒントは非常に侵襲的です。日付列のインデックスなど、いくつかの簡単なインデックスの変更を最初に試す必要があります。
usr

@usr 1秒未満で実行される単純なPK結合です。ここでかなり安全な賭け。
パパラッチ

4
ハッシュ結合を強制する場合、大きなテーブルのスキャンを強制します。より良いオプションがあります。
ロブファーリー

30

で正しいプランを取得できるのでORDER BY、おそらく自分のTOPオペレーターを転がすことができますか?

SELECT DOCUMENT_ID, COPIES, REQUESTOR, D_ID, FILE_NUMBER
FROM (
    SELECT dc.DOCUMENT_ID,
           dc.COPIES,
           dc.REQUESTOR,
           dc.D_ID,
           cj.FILE_NUMBER,
           ROW_NUMBER() OVER (ORDER BY cj.FILE_NUMBER) AS _rownum
    FROM DOCUMENT_QUEUE dc
    INNER JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
    WHERE dc.QUEUE_DATE <= GETDATE()
      AND dc.PRINT_LOCATION = 2
) AS sub
WHERE _rownum=1;

私の考えでは、ROW_NUMBER()上記のクエリプランはを持っている場合と同じである必要がありORDER BYます。これで、クエリプランにはセグメント、シーケンスプロジェクト、最後にフィルター演算子が含まれるようになり、残りは適切なプランのようになります。


3
実際、トップオペレータ(およびその他の多くの要素(シーケンスプロジェクト、セグメント、ソート))を提供しましたが、それでも1秒未満で実行されました。@frisbeeが最初だったので、@ frisbeeに正しい答えを与えるつもりです。素晴らしい答え。
ケネスフィッシャー

10
@KennethFisher、フリスビーの答えはよりシンプルですが、スレッジハンマーが標準のフレーミングハンマーよりも簡単に仕上げ釘を打つ方法です。また、特に長時間にわたって所定の場所に置いておくと、多くのリスクが伴います。私はそのようなヒントをテストで使用することはありませんが、多分、フリンジ例外かもしれません。
スティーブMangiameli

@SteveMangiameliこの特定のケースでは、結合が1つしかないため、多くの懸念がなくなります。結合ヒント(またはクエリヒント)を使用することのリスクを認識しています。この場合は正当化されると思います。
ケネスフィッシャー

5
@KennethFisher Imo、クエリヒントの主なリスクは、データが増加または変更されると、施行するクエリプランがシステムが単独で見つけたものよりも悪化する可能性があることです。計画の小さな間違いがパフォーマンスに深刻な影響を与える可能性があることは既に説明しました。実稼働環境でヒントを使用することは、「このプランが常に最適であり、実稼働環境でこのクエリの存続期間にわたってプランナーとデータがどのように動作するかを十分に理解しているため、常に最善である」と宣言しています。クエリについて自信を持ったことがありません。
jpmc26

29

編集:+1 FILE_NUMBERは整数のゼロパディング文字列バージョンであることが判明したため、この状況で機能します。ここでの文字列のより良い解決策は''、値を追加すると順序に影響する可能性があるため、(空の文字列)を追加することsign(rand()+1)です。「並べ替え」のアイデアはここでも有効です。私の方法が理想的ではなかったというだけです。

+1

いいえ、私は何かに同意するという意味ではなく、解決策としてそれを意味します。あなたがあなたのクエリを変更した場合ORDER BY cj.FILE_NUMBER + 1、その後TOP 1異なる動作をします。

順序付けされたクエリに対して小さな行の目標が設定されていると、システムはデータを順番に消費しようとし、ソート演算子がないようにします。また、ハッシュテーブルの作成を回避し、おそらく最初の行を見つけるためにあまり多くの作業を行う必要はないと考えます。あなたの場合、これは間違っています-それらの矢印の太さから、単一の一致を見つけるために多くのデータを消費しなければならないようです。

これらの矢印の太さは、DOCUMENT_QUEUE(DQ)テーブルがCORRESPONDENCE_JOURNAL(CJ)テーブルよりもはるかに小さいことを示しています。そして、最良の計画は、実際には、CJ行が見つかるまでDQ行をチェックすることです。確かに、クエリオプティマイザー(QO)がこのような厄介な処理を行わなかった場合、それはORDER BYCJのカバーインデックスによってうまくサポートされます。

したがって、ORDER BY完全にドロップした場合、ネストループを含む計画が得られ、DQの行を反復処理し、CJを探して行が存在することを確認します。そして、でTOP 1、これは単一の行がプルされた後に停止します。

しかし、あなたは実際に最初の行を必要とするならばFILE_NUMBER行うことにより、その有用であることが、あなたが(間違って)そうです、そのインデックスを無視にシステムをだますことができ、順序ORDER BY CJ.FILE_NUMBER+1-我々は前と同じ順序を維持します知っているが、重要なのはQOしません。QOは、上位Nのソート演算子が満たされるように、全体を設定することに集中します。このメソッドは、順序の値を計算するCompute Scalar演算子と、最初の行を取得するTop N Sort演算子を含むプランを作成する必要があります。しかし、これらの右側には、CJで多くのシークを行っている素敵なネストループが表示されます。また、DQの何とも一致しない行の大きなテーブルを実行するよりもパフォーマンスが向上します。

ハッシュマッチは必ずしもひどいものではありませんが、DQから返される行のセットがCJよりもずっと小さい場合(予想どおり)、ハッシュマッチはCJの多くをスキャンします必要以上に。

注:+0ではなく+1を使用しました。クエリオプティマイザーは、+ 0が何も変更しないことを認識する可能性が高いためです。もちろん、同じことが、現在ではなく、将来のある時点で+1に適用される可能性があります。


7

この投稿の結果を読んで、行の目標などの概念を理解しています。私が興味を持っているのは、より良いプランを使用するためにクエリを変更する方法です。

追加するOPTION (QUERYTRACEON 4138)と、最終的なプランについて過度に規範的になることなく、そのクエリのみの行ゴールの効果がオフになり、おそらく最も単純/最も直接的な方法になります。

このヒントを追加するとアクセス許可エラーが発生する場合(に必要DBCC TRACEON)、プランガイドを使用して適用できます。

QUERYTRACEONスパゲッティによるプランガイドでの使用

...または単にストアドプロシージャを使用します。

どんな許可がQUERYTRACEON必要ですか?ケンドラ・リトル


3

SQL Serverの新しいバージョンは、オプティマイザーが行の目標の最適化を適用できる場合に最適以下のパフォーマンスを得るクエリを処理するための異なる(ほぼ間違いなく優れた)オプションを提供します。SQL Server 2016 SP1では、DISABLE_OPTIMIZER_ROWGOAL USE HINTトレースフラグ4138と同じ効果があります。そのバージョンを使用していない場合は、OPTIMIZE FORクエリヒントを使用して、1だけではなくすべての行を返すように設計されたクエリプランを取得することも検討できますは、問題の結果と同じ結果を返しますが、1行だけを取得することを目的として作成されません。

DECLARE @top INT = 1;

SELECT TOP (@top) dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
OPTION (OPTIMIZE FOR (@top = 987654321));

2

を実行しているので、最初に決定論をTOP(1)作成するORDER BYことをお勧めします。少なくともこれにより、結果が機能的に予測可能になります(常に回帰テストに役立ちます)。あなたが追加する必要があるように見えますDC.D_IDし、CJ.CORRESPONDENCE_IDそのために。

問い合わせ計画を見ると、私は時々 、クエリを簡素化するために有益見つける:おそらく、事前に一時テーブルに関連するすべての直流行を選択する上で基数推定の問題を解消するQUEUE_DATEPRINT_LOCATION。行数が少ない場合、これは高速です。必要に応じて、永続テーブルを変更せずに、この一時テーブルにインデックスを追加できます。

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