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


16

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

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秒で完了します。

上記のクエリが値を返す場合、ストアドプロシージャを終了する必要があるため、次のように書き直しました。

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を使用した遅い計画計画の貼り付けBrentozarを使用した
    速い計画計画の貼り付け

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

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

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


回答:


18

によって説明されてきたポール・ホワイト彼のブログの記事で:インサイドオプティマイザ:深さの行目標EXISTS紹介好む行の目標、NESTED LOOPSまたはMERGE JOINオーバーHASH MATCH

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

クエリでは、これがネストされたループを導入し、並列処理を削除するため、どうやら計画が遅くなるようです。

そのため、クエリを使用せずにクエリを書き換える方法を見つける必要があるでしょうNOT EXISTS

を使用してクエリを書き直しLEFT OUTER JOIN、smalltableに行がないことを確認するには、NULL

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
)

断っておくが、アーロン・ベルトランは、ブログの記事がある彼はEXISTSを好まない理由を提供しますが、他のアプローチは、より良い仕事、およびNULL値の場合の潜在的な正しさの問題を認識するかどうかを確認するために読み通しべきであるが。

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


0

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

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を使用すると、速度が向上します。


あなたよりも、それをテストします
TheGameiswar

0

私は同じ問題に出くわしました。「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の代わりにネストされたループを使用するクエリがトリガーされます一致。なぜそれが起こっているのかが興味深い理由を調べて、その理由を調べていない。

お役に立てば幸いです!

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