SQL Selectの実行に時間がかかりすぎる


9

これは一時テーブルからの単純な選択であり、既存のテーブルを主キーに結合したままにします。結合されたテーブルを参照するトップ1を使用する2つのサブ選択があります。

コードで:

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn1 AND 
    TempTable.PKColumn2 = JoinedTable.PKColumn2)
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

これは私のクエリの正確なレプリカです。

2つのサブ選択を削除すると、うまく実行されます。2つのサブ選択を使用すると、1秒あたり約100レコードを取得します。これは、ほぼ100万のレコードを返す必要があるため、このクエリでは非常に低速です。

すべてのテーブルに主キーがあるかどうかを確認しましたが、すべて主キーがあります。それらはすべて、それらのWHERE句にあるものやJOIN句にあるもののように、重要な列のインデックスと統計を持っています。主キーもインデックスも定義されていない唯一のテーブルは一時テーブルですが、遅い副選択に関連するものではないため問題でもありません。前述したように、副選択がないため、問題なく実行されます。

これらがないTOP 1と、複数の結果が返され、エラーが発生します。

誰か助けて?

編集

そのため、実行計画では、インデックスが不足していると言われました。私はそれを作成し、他のいくつかのインデックスを再作成しました。しばらくすると、実行プランはそれらを使用していたため、クエリは高速に実行されます。唯一の問題は、同じクエリに対して、別のサーバーでこれを再度実行できないことです。したがって、私の解決策は、SQL Serverが使用するインデックスをHINTすることです。


うわー、それは印象的です。しかし、代わりにこれを複数の個別のステートメントに分割できますか?また、代わりにストアドプロシージャはどうですか?

2
@Adelその選択は、実際にはストアドプロシージャ内のサブ選択です。実際には全体がかなり大きいですが、実行に時間がかかる正確な部分だと100%確信しています。

自動的に選択されたインデックスを含む実行プランの変更は、ほとんどの場合、データの変更に関係しています。インデックスが完全にカバーされていることを確認します。そうしないと、エンジンがテーブルスキャンなどの予期しないパスを使用します。新しいサーバーの実行計画(ヒントなし)を確認して、元のシステムからの逸脱が発生している場所を確認することをお勧めします。
Robert Miller

そうですか。私はサーバーを変更しただけで、データベースは同じで、インデックスも同じです。それでも、インデックスの使用を自動的に選択したようには見えません。それはあなたが言ったことを正確に行います:テーブルスキャンです。
スマール、2011年

クエリオプティマイザは、クエリのテーブルのインデックスを好きではないようです。実行プランは欠落しているインデックスを示しましたか?
Robert Miller

回答:


7

100万件のレコードクエリでは、のようなものを避ける必要があると思いOUTER JOINSます。のUNION ALL代わりに使用することをお勧めしますLEFT JOINCROSS APPLYselect句のサブクエリよりも効率的であると思う限り、正しいと思うConard Frixによって書かれたクエリを変更します。

今:クエリの変更を開始したときに、WHERE句が次のようになっていることに気付きました JoinedTable.WhereColumn IN (1, 3)。この場合、フィールドがnullの場合、条件はfalseになります。では、null値の行をフィルタリングしているときに、なぜLEFT JOINを使用しているのでしょうか。ただ置き換えLEFT JOINではINNER JOIN、私はそれが速くなることを保証します。

INDEXについて:

テーブルにインデックスがある場合は、

table1(a int, b nvarchar)

そしてあなたのインデックスは:

nonclustered index ix1 on table1(a)

そしてあなたはこのようなことをしたいです:

select a,b from table1
where a < 10

インデックスに列を含めていないbので、どうなりますか?

sql-serverがインデックスを使用する場合、「インデックスシーク」と呼ばれるインデックスを検索し、メインテーブルを参照して「ルックアップ」bと呼ばれる列を取得する必要があります。この手順は、テーブル自体をスキャンするよりもはるかに時間がかかる場合があります:"テーブルスキャン"

しかし、sql-serverが持っている統計に基づいて、そのような状況では、インデックスをまったく使用しない可能性があります。

そのため、まずExecution Planインデックスを使用しているかどうかを確認してください。

はいまたはいいえ両方の場合、選択しているすべての列が含まれるようにインデックスを変更します。次のように言います:

nonclustered index ix1 on table1(a) include(b)

この場合、Look Upは必要なく、クエリは非常に高速に実行されます。


1
その左結合を内部結合に変更することはできません。結果が台無しになります。これはビジネスルールです。2番目のテーブルは必ずしも関連レコードを持つ必要はありません。また、WHERE句の列はnull値を受け入れません。
スマー

6

そのサブ選択は、遅い選択を引き起こしている列選択で選択します。左結合で副選択を使用するか、以下で定義した派生テーブルを使用する必要があります。

3番目のテーブルの2つのインスタンスへの左結合の使用

SELECT
  TempTable.Col1,
  TempTable.Col2,
  TempTable.Col3,
  JoinedTable.Col1,
  JoinedTable.Col2,
  ThirdTable.Col1 AS ThirdTableColumn1,
  ThirdTable2.Col1 AS ThirdTableColumn2
FROM #TempTable as TempTable
LEFT JOIN JoinedTable ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTable.PKColumn2)
LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

派生テーブルの使用

 SELECT 
      TempTable.Col1,
      TempTable.Col2,
      TempTable.Col3,
      DerivedTable.Col1,
      DerivedTable.Col2,
      DerivedTable.ThirdTableColumn1,
      DerivedTable.ThirdTableColumn2
 FROM #TempTable as TempTable
    LEFT JOIN (SELECT
                 JoinedTable.PKColumn2,
                 JoinedTable.Col1,
                 JoinedTable.Col2,
                 JoinedTable.WhereColumn,
                 ThirdTable.Col1 AS ThirdTableColumn1,
                 ThirdTable2.Col1 AS ThirdTableColumn2
               FROM JoinedTable
               LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
               LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn) 
        DerivedTable ON (TempTable.PKColumn1 = DerivedTable .PKColumn2 AND 
        TempTable.PKColumn2 = DerivedTable.PKColumn2)
    WHERE
        DerivedTable.WhereColumn IN  (1, 3)

2

代わりにクロス適用を試してください

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    ThirdTableColumn1.col1,
    ThirdTableColumn2.col1

FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTablePKColumn2)

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

CTEとrow_number、またはMINを使用したインラインクエリを使用することもできます。


2

JOINビットを句の主要部分から移動し、副選択として配置します。WHEREおよびJOINセクションに移動すると、TOP 1を何度も選択する必要がないことが保証されます。これが遅い理由です。これを確認したい場合は、実行計画を確認してください。


2

ThirdTable参照、(あなたの例では、サブ・セレクト)は、クエリの他の部分と同じインデックス注意が必要です。

副選択を使用するかどうかに関係なく:

(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeColumn = JoinedTable.SomeColumn
) as ThirdTableColumn1,
(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
) as ThirdTableColumn2,

LEFT JOINS(John Hartsockの提案による):

LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn

CROSS APPLY(Conrad Frixの提案による):

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2

covering indexesについて定義されていることThirdTable.SomeColumnThirdTable.SomeOtherColumnおよびインデックスが一意であることを確認する必要があります。つまりThirdTable、複数行の選択を排除してパフォーマンスを向上させるには、参照をさらに限定する必要があります。選択はsub selectsLEFT JOINまたはCROSS APPLYあなたが選択性を改善するまで、本当に問題ではないだろうThirdTable.SomeColumnThirdTable.SomeOtherColumnのユニークな選択性を確保するために複数の列を含むことによって。それまでは、引き続きパフォーマンスが低下することを期待しています。

このcovering indexトピックはMaziar Taheriがうまく紹介しています。彼の仕事は繰り返さないが、カバーインデックスの使用を心に留める必要性を強調する。

つまり、関連するテーブル内の列を追加して一意の行の一致を保証することにより、ThirdTable.SomeColumnおよびThirdTable.SomeOtherColumnクエリ(または結合)の選択性を向上させます。これが不可能な場合は、エンジンが行のプルでビジー状態になるため、引き続き破棄されるため、パフォーマンスの問題が引き続き発生します。これは、I / O、CPU、そして最終的には実行計画に影響を与えます。

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