web-dev-qa-db-ja.com

IF EXISTSでクエリをラップすると非常に遅くなる

私は以下のクエリを持っています:

select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)

上記のクエリは3秒で完了します。

上記のクエリが値を返す場合、ストアドプロシージャをEXITにしたいので、以下のように書き直しました。

If Exists(
select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
)
Begin
Raiserror('Source missing',16,1)
Return
End

ただし、これには10分かかります。

上記のクエリを次のように書き換えることもできます。これも3秒未満で完了します。

  select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source
if @@rowcount >0
Begin
Raiserror('Source missing',16,1)
Return
End

上記の書き換えの問題は、上記のクエリがより大きなストアドプロシージャの一部であり、複数の結果セットを返すことです。 C#では、各結果セットを反復処理し、いくつかの処理を行います。

上記は空の結果セットを返すため、このアプローチを使用する場合は、C#を変更して再度デプロイする必要があります。

だから私の質問は、

なぜIF EXISTS計画を変更して、非常に時間がかかりますか?

以下はあなたを助けるかもしれない詳細であり、あなたが何か詳細が必要な場合は私に知らせてください:

  1. 私と同じ計画を得るためにテーブルと統計スクリプトを作成します
  2. スロー実行計画
  3. 高速実行計画

    Brentozar Paste the planを使用したスロープラン
    ブレントザーを使用した高速プランプランを貼り付けます

注:両方のクエリは同じ(パラメーターを使用)で、唯一の違いはEXISTSです(ただし、匿名化中にいくつかの間違いをした可能性があります) )。

テーブル作成スクリプトは次のとおりです。

http://Pastebin.com/CgSHeqXc -小さなテーブル統計
http://Pastebin.com/GUu9KfpS -大きなテーブル統計

16
TheGameiswar

Paul White のブログ記事で説明されているように、 オプティマイザの内部:行の目標の詳細EXISTSは、行の目標を導入し、_NESTED LOOPS_または_MERGE JOIN_ over _HASH MATCH_

最後の例として、論理的な準結合(EXISTSで導入されたサブクエリなど)が全体的なテーマを共有していることを考慮してください。最初に一致する行をすばやく見つけるために最適化する必要があります。

クエリでは、これによりネストされたループが導入され、並列処理が削除されるため、計画が遅くなります。

したがって、クエリから_NOT EXISTS_を使用せずにクエリを書き換える方法を見つける必要があるでしょう。

_LEFT OUTER JOIN_を使用してクエリを書き直し、NULLをテストすることでsmalltableに行がないことを確認することで問題が解決する場合があります

_If EXISTS(
    SELECT databasename
    FROM somedb.dbo.bigtable l
    LEFT JOIN dbo.smalltable c ON c.source = l.source
    WHERE databasename = 'someval'
    AND source <> 'kt'
    AND c.source IS NULL
)
_

次のように、比較する必要のあるフィールドの数に応じて、おそらくEXCEPTクエリも使用できます。

_If EXISTS(
   SELECT source
   FROM somedb.dbo.bigtable l
   WHERE databasename = 'someval'
   AND source <> 'kt'

   EXCEPT

   SELECT source
   FROM dbo.smalltable
)
_

あなたに注意してください Aaron Bertrand ブログ投稿 彼が存在しないことを好む理由を提供 他のアプローチがよりうまく機能するかどうかを確認し、その可能性を認識するために読む必要がありますNULL値の場合の正確性の問題。

関連するQ&A: 埋め込まれたselectステートメントよりも時間がかかる場合

私は同じ問題に遭遇しましたが、「EXISTS」の使用を避け、「COUNT()」関数と「IF ... ELSE」ステートメントを使用することで、自分自身で対処することができました。

あなたの例では、以下を試してください:

IF
(
    SELECT
        COUNT(l.databasename) + 1 AS databasename
    FROM somedb.dbo.bigtable AS l

    WHERE   l.databasename ='someval'
        AND l.[source]  <> 'kt'
        AND NOT EXISTS(SELECT 1 FROM dbo.smalltable AS c WHERE c.[source]=l.[source])
) > 1 --Acts like EXISTS
BEGIN
    RAISERROR('Source missing', 16, 1)
RETURN
END

カウントに「+ 1」を追加する理由は、IF条件で "> 1"を使用できるようにするためです。 "> 0"または "<> 0"を使用すると、クエリがトリガーされ、HASHではなくネストされたループが使用されます。一致。なぜそれが正確に行われているのかを調べていないのであれば、その理由を知るのは興味深いでしょう。

お役に立てば幸いです。

0
Hayder Nahee

明示的な結合を使用してクエリを書き換え、次のように使用する結合操作(ループ、ハッシュ、またはマージ)を指定する必要があります。

If not exists(
    select databasename 
    from somedb.dbo.bigtable l
    inner hash join dbo.smalltable c 
        on c.source = l.source
where databasename ='someval' and source  <>'kt')
begin
    Raiserror('Source missing',16,1)
    Return
end

EXISTSまたはNOT EXISTSを使用すると、SQL Serverは、NESTED LOOP操作を使用してクエリプランを生成します。これは、条件を満たすすべての行を1つずつ検索して、条件を満たす最初の行を探す必要があることを前提としています。 HASH JOINを使用するとスピードアップします。

0
Artem Machnev