存在する場合、埋め込みSELECTステートメントよりも時間がかかります


35

次のコードを実行すると、22.5分かかり、1億6千万回の読み取りを行います。ただし、内側のselectステートメントだけを実行すると、15秒しかかからず、264kの読み取りが実行されます。補足として、選択クエリはレコードを返しません。

なぜIF EXISTSそれが非常に長く実行され、非常に多くの読み取りを行うのでしょうか?select文も変更しSELECT TOP 1 [dlc].[id]て、2分後に殺しました。

一時的な修正として、count(*)を実行し、その値を変数に割り当てるように変更しました@cnt。次に、IF 0 <> @cntステートメントを実行します。しかしEXISTS、selectステートメントでレコードが返された場合、少なくとも1つのレコードが見つかったらスキャン/シークの実行を停止count(*)し、完全なクエリを完了するため、より良いと思いました。私は何が欠けていますか?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END

4
行ゴールの問題を回避するために、別のアイデア(未検証、気にしないでください!)は逆-を試すことIF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> ENDです。
アーロンバートランド

回答:


32

なぜIF EXISTSそれが非常に長く実行され、非常に多くの読み取りを行うのでしょうか?select文も変更しSELECT TOP 1 [dlc].[id]て、2分後に殺しました。

この関連する質問への回答で説明したように:

TOPは実行計画にどのように(そしてなぜ)影響しますか?

を使用EXISTSすると、行の目標が導入されます。オプティマイザーは、最初の行をすばやく見つけることを目的とした実行計画を作成します。これを行う際、データが均一に分散されていると想定しています。たとえば、100,000行に100個の予想される一致があると統計が示す場合、最初の一致を見つけるために1,000行のみを読み取る必要があると想定します。

この仮定が誤りであることが判明した場合、これは予想される実行時間よりも長くなります。たとえば、SQL Serverが検索で最初に一致した値を見つけるためにたまたまアクセス方法(順不同スキャンなど)を選択した場合、ほぼ完全なスキャンになる可能性があります。一方、最初の数行で一致する行が見つかった場合、パフォーマンスは非常に良好です。これは、行の目標に関する基本的なリスクであり、一貫性のないパフォーマンスです。

一時的な修正として、count(*)を実行し、その値を変数に割り当てるように変更しました

通常、行の目標が割り当てられないようにクエリを再構成することが可能です。行の目標がない場合、最初に一致する行が検出されたときにクエリを終了できます(正しく記述されている場合)が、実行計画の戦略は異なる可能性があります(願わくば、より効果的です)。明らかに、count(*)はすべての行を読み取る必要があるため、完全な代替ではありません。

SQL Server 2008 R2以降を実行している場合、一般に文書化されサポートされているトレースフラグ4138を使用して、行の目標なしで実行プランを取得することもできます。このフラグは、サポート されているヒントを使用して指定することもできますが、プランガイドで使用する場合を除き、OPTION (QUERYTRACEON 4138)ランタイムsysadmin権限が必要であることに注意してください。

あいにく

上記のどれもIF EXISTS条件文では機能しません。通常のDMLにのみ適用されます。これSELECT TOP (1)試した別の製剤で機能します。COUNT(*)前述のように、すべての修飾された行をカウントする必要があるを使用するよりも、それが良いかもしれません。

とはいえ、この要件を表現する方法はいくつもあり、行の目標を回避または制御できると同時に、検索を早期に終了できます。最後の例:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;

提供された代替例は3.75分で実行され、46mの読み取りを実行しました。したがって、元のクエリよりも高速ですが、この場合は@cnt = count(*)に固執し、後で変数を評価すると思います。特に、これが実行される時間の99%には何もありません。あなたとロブの答えに基づいて、Existsは、何らかの結果を本当に期待し、その結果がデータ内に均等に分散されている場合にのみ良いというように聞こえます。
クリスウッズ

3
@ChrisWoods:あなたは「特に、これが実行される時間の99%には何もありません」と言っていました。通常、行は存在せず、行がないことを見つけるためにすべてをスキャンする必要があるため、行の目標が悪い考えであることをほぼ保証します。巧妙なインデックスを追加できない場合は、COUNT(*)を使用してください。
ロスプレッサー

25

EXISTSは単一の行を見つけるだけでよいため、1つの行目標を使用します。これにより、理想的でない計画が作成される場合があります。あなたがそれがあなたのためにそのようになると予想する場合、変数の結果を入力し、COUNT(*)それが0より大きいかどうかを確認するためにその変数をテストします。

だから...小さな行の目標では、ハッシュテーブルの構築や、マージ結合に役立つフローのソートなどのブロック操作を回避しますそれが何かを見つけた場合に最適です。これは、セット全体ではるかに悪い計画を立てることができることを除いて。単一の行をすばやく見つけるには、ブロックを回避するためのこの方法が必要です...

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