かなり単純なクエリがあります
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
がクエリプランを変更したことは、私を驚かせませんでした。それが、それによって事態が大幅に悪化したことに少し驚いています。
注:この post の結果を読み、Row Goal
などの概念を理解しました。私が興味を持っているのは、使用するようにクエリを変更する方法です。より良い計画。現在、私はデータを一時テーブルにダンプし、それから最初の行を取り出しています。より良い方法があるかどうか私は思っています。
Edit事実の後でこれを読んでいる人のために、ここにいくつかの追加情報があります。
私が始めたとき、他のインデックスはありませんでした。 Correspondence_Journal(Document_Id、File_Number)で1つになりました
ハッシュ 結合を強制してみてください*
_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のパフォーマンスが低い可能性があります。
* join hints に注意してください。プランテーブルのアクセス順序がクエリ内のテーブルのwritten順序と一致するように強制するためです。 (OPTION (FORCE ORDER)
が指定されたかのように)。ドキュメントのリンクから:
これは、例では望ましくない影響を生成しない可能性がありますが、一般的には非常に良い可能性があります。 _FORCE ORDER
_(暗黙的または明示的)は、very強力なヒントであり、強制的な順序を超えています。部分的な集計や並べ替えなど、幅広いオプティマイザー手法が適用されるのを防ぎます。
OPTION (HASH JOIN)
queryヒントは、_FORCE ORDER
_を意味するものではないため、適切なケースではそれほど煩わしくないかもしれません。ただし、クエリのすべての結合に適用されます。他のソリューションが利用可能です。
_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
_がある場合と同じでなければなりません。クエリプランには、セグメント、シーケンスプロジェクト、最後にフィルターオペレーターが含まれているはずです。残りは、適切なプランのように見えるはずです。
編集:+1はこの状況で機能します。これは、_FILE_NUMBER
_が整数のゼロでパディングされた文字列バージョンであることが判明したためです。文字列のここでのより良い解決策は、_''
_(空の文字列)、値を追加すると順序に影響する可能性があるため、または数値が定数であるがsign(Rand()+1)
などの非決定的関数を含むものを追加する可能性があるため、「ソートの解除」の考え方は依然として有効ですここでは、私の方法が理想的ではなかったというだけです。
+1
いいえ、私は何かに同意しているわけではありません。それは解決策としてです。クエリを_ORDER BY cj.FILE_NUMBER + 1
_に変更すると、_TOP 1
_の動作が異なります。
順序付けられたクエリに対して小さな行の目標が設定されていると、並べ替え演算子が存在しないようにするために、システムはデータを順番に消費しようとします。また、ハッシュテーブルの作成も回避され、最初の行を見つけるために多くの作業を行う必要がないことがわかります。あなたの場合、これは間違っています-それらの矢印の太さから、単一の一致を見つけるには大量のデータを消費する必要があるようです。
これらの矢印の太さは、_DOCUMENT_QUEUE
_(DQ)テーブルが_CORRESPONDENCE_JOURNAL
_(CJ)テーブルよりもはるかに小さいことを示しています。そして、最良の計画は、実際にはCJ行が見つかるまでDQ行をチェックすることです。実際、この厄介な_ORDER BY
_がそこにない場合、クエリオプティマイザー(QO)はこれを実行します。これは、CJのカバーインデックスによって適切にサポートされます。
したがって、_ORDER BY
_を完全に削除した場合、ネストされたループを含む計画を取得し、DQの行を反復処理して、CJにシークして行が存在することを確認すると思います。そして、_TOP 1
_を使用すると、これは単一の行がプルされた後に停止します。
しかし、実際に_FILE_NUMBER
_順の最初の行が必要な場合は、_ORDER BY CJ.FILE_NUMBER+1
_を実行することで、システムをだまして(誤って)非常に役立つと思われるインデックスを無視させることができます。以前と同じ順序ですが、QOはそうではありません。 QOは全体の設定に重点を置くため、上位N並べ替え演算子を満足させることができます。このメソッドは、順序付けの値を計算するためのCompute Scalar演算子と、最初の行を取得するためのTop N Sort演算子を含むプランを生成する必要があります。しかし、これらの右側には、CJで多くのSeeksを実行する、Nice Nested Loopが表示されます。また、DQの何にも一致しない行の大きなテーブルを実行するよりもパフォーマンスが優れています。
ハッシュマッチは必ずしもひどいわけではありませんが、DQから返される行のセットがCJよりもはるかに小さい場合(予想どおり)、ハッシュマッチはより多くのCJをスキャンします。必要以上に。
注:クエリオプティマイザーは+0は何も変更しないと認識する可能性が高いため、+ 0ではなく+1を使用しました。もちろん、同じことが+1にも当てはまるかもしれませんが、現在ではない場合でも、将来のある時点で当てはまります。
私はこの投稿の結果を読み、行の目標などの概念を理解しました。私が気になるのは、より良い計画を使用するようにクエリを変更する方法です。
OPTION (QUERYTRACEON 4138)
を追加すると、最終的な計画について過度に規定せずに、そのクエリのみの行目標の効果がオフになり、おそらく最も簡単で直接的な方法になります。
このヒントを追加すると、アクセス許可エラーが発生する場合(DBCC TRACEON
)、プランガイドを使用して適用できます。
プランガイドでのQUERYTRACEON
の使用spaghettidba による
...または単にストアドプロシージャを使用します。
SQL Serverの新しいバージョンでは、オプティマイザが行の目標の最適化を適用できる場合に、次善のパフォーマンスを得るクエリを処理するためのさまざまな(間違いなく優れた)オプションが提供されています。 SQL Server 2016 SP1はDISABLE_OPTIMIZER_ROWGOAL USE HINT
を導入しました。これはトレースフラグ4138と同じ効果があります。そのバージョンを使用していない場合は、OPTIMIZE FOR
クエリヒントを使用して返すように設計されたクエリプランを取得することもできます。以下のクエリは、質問の結果と同じ結果を返しますが、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));
TOP(1)
を実行しているので、ORDER BY
を最初に決定論的にすることをお勧めします。少なくとも、これは結果が機能的に予測可能であることを保証します(常に回帰テストに役立ちます)。 DC.D_ID
とCJ.CORRESPONDENCE_ID
を追加する必要があるようです。
クエリプランを見ると、クエリを簡略化することが役立つ場合があります。関連するすべてのDC行を一時テーブルに事前に選択して、QUEUE_DATE
とPRINT_LOCATION
のカーディナリティの推定に関する問題を排除することができます。行数が少ない場合、これは高速です。その後、永続テーブルを変更せずに、必要に応じてこの一時テーブルにインデックスを追加できます。