sql >> データベース >  >> RDS >> Sqlserver

SQLServerでのテーブル変数のパフォーマンス

    この記事では、テーブル変数のパフォーマンスのトピックに触れます。 SQL Serverでは、完全なテーブルとして機能する変数を作成できます。おそらく、他のデータベースにも同じ機能がありますが、私はそのような変数をMSSQLServerでのみ使用しました。

    したがって、次のように書くことができます。

    declare @t as table (int value)

    ここでは、@t変数をIntegerタイプの単一のValue列を含むテーブルとして宣言します。より複雑なテーブルを作成することも可能ですが、この例では、最適化を検討するには1つの列で十分です。

    これで、この変数をクエリで使用できます。それに多くのデータを追加し、この変数からデータ検索を実行できます:

    insert into @t
    select UserID
    from User
    or
    select * from @t

    大規模な選択のためにデータをフェッチする必要がある場合に、テーブル変数が使用されることに気付きました。たとえば、サイトのユーザーを返すクエリがコードにあります。これで、すべてのユーザーのIDを収集し、それらをテーブル変数に追加して、これらのユーザーのアドレスを検索できます。おそらく、データベースに対して1つのクエリを実行せず、すべてをすぐに取得しない理由を誰かが尋ねる可能性があります。簡単な例があります。

    ユーザーのアドレスがデータベースに保存されているときに、ユーザーがWebサービスからアクセスするとします。この場合、逃げ道はありません。サービスから多数のユーザーIDを取得しましたが、データベースへのクエリを回避するために、すべてのIDをテーブル変数としてクエリパラメータに追加する方が簡単で、クエリがきれいに見えると誰かが判断しました。

    select *
    from @t as users 
       join Address a on a.UserID = users.UserID
    os

    これはすべて正しく機能します。 C#コードでは、LINQを使用して、両方のデータ配列の結果を1つのオブジェクトにすばやく組み合わせることができます。ただし、クエリのパフォーマンスが低下する可能性があります。

    実際のところ、テーブル変数は大量のデータを処理するために設計されたものではありません。私が間違っていなければ、クエリオプティマイザは常にLOOP実行メソッドを使用します。したがって、@ tからのIDごとに、アドレステーブルでの検索が発生します。 @tに1000レコードある場合、サーバーはアドレスを1000回スキャンします。

    実行に関しては、スキャンの数が非常に多いため、サーバーはデータの検索を停止するだけです。

    アドレステーブル全体をスキャンして、すべてのユーザーを一度に見つける方がはるかに効果的です。このメソッドはMERGEと呼ばれます。ただし、ソートされたデータが多い場合は、SQLServerが選択します。この場合、オプティマイザーは、変数に追加されるデータの量と量、およびそのような変数にインデックスが含まれていないために並べ替えがあるかどうかを知りません。

    テーブル変数にデータがほとんどなく、何千もの行を挿入しない場合は、すべて問題ありません。ただし、そのような変数を使用して大量のデータを追加する場合は、読み続ける必要があります。

    テーブル変数をSQLに置き換えても、クエリのパフォーマンスが大幅に向上します。

    select *
    from (
     Select 10377 as UserID
     Union all
     Select 73736
     Union all
     Select 7474748
     ….
      ) as users 
       join Address a on a.UserID = users.UserID

    このようなSELECTステートメントは数千に及ぶ可能性があり、クエリテキストは膨大になりますが、SQL Serverは効果的な実行プランを選択できるため、大量のデータに対して数千倍高速に実行されます。

    このクエリは見栄えがよくありません。ただし、IDを1つだけ変更するとクエリテキスト全体も変更され、パラメータを使用できないため、実行プランをキャッシュできません。

    Microsoftは、ユーザーがこのように表形式の変数を使用することを期待していなかったと思いますが、優れた回避策があります。

    この問題を解決する方法はいくつかあります。ただし、私の意見では、パフォーマンスの点で最も効果的なのは、クエリの最後にOPTION(RECOMPILE)を追加することです。

    select *
    from @t as users 
       join Address a on a.UserID = users.UserID
    OPTION (RECOMPILE)

    このオプションは、ORDER BYに続いて、クエリの最後に1回追加されます。このオプションの目的は、実行のたびにSQLServerにクエリを再コンパイルさせることです。

    その後、クエリのパフォーマンスを測定すると、検索の実行時間が短縮される可能性があります。データが大きい場合、パフォーマンスが大幅に向上する可能性があり、数十分から数秒になります。現在、サーバーは各クエリを実行する前にコードをコンパイルし、キャッシュからの実行プランを使用しませんが、変数内のデータの量に応じて新しいプランを生成します。これは通常、非常に役立ちます。

    欠点は、実行プランが保存されておらず、サーバーがクエリをコンパイルして、毎回効果的な実行プランを探す必要があることです。ただし、このプロセスに100ミリ秒以上かかったクエリは見たことがありません。

    テーブル変数を使用するのは悪い考えですか?いいえそうではありません。それらは大きなデータ用に作成されたものではないことを覚えておいてください。データが多い場合は、一時テーブルを作成してこのテーブルにデータを挿入したり、その場でインデックスを作成したりする方がよい場合もあります。一度だけですが、私はレポートでこれをしなければなりませんでした。当時、私は1つのレポートを生成する時間を3時間から20分に短縮しました。

    複数のクエリに分割して結果を変数に格納するのではなく、1つの大きなクエリを使用する方が好きです。 SQL Serverが大きなクエリのパフォーマンスを調整できるようにすると、失望することはありません。テーブル変数は、その利点を実際に確認できる極端な場合にのみ使用する必要があることに注意してください。


    1. クエリよりも多くの値を返すプロシージャ内のカーソル

    2. Oracleでカレンダーテーブルにデータを入力する方法は?

    3. Perconaサーバーをハイブリッドクラウドにデプロイする

    4. スタンバイをOracleのプライマリデータベースと同期する手順