web-dev-qa-db-ja.com

CTE、サブクエリ、一時テーブル、またはテーブル変数にパフォーマンスの違いはありますか?

この優れた SOの質問 では、CTEsub-queriesの違いについて説明しました。

私は具体的に尋ねたいです:

次の各機能は、どのような状況でより効率的/高速ですか?

  • CTE
  • サブクエリ
  • 一時テーブル
  • テーブル変数

伝統的に、私はtemp tablesの開発に多くのstored proceduresを使用しました-それらはたくさんの絡み合ったサブクエリよりも読みやすいようです。

Non-recursive CTEsはデータのセットを非常にうまくカプセル化し、非常に読みやすいですが、常にパフォーマンスが向上すると言える特定の状況はありますか?それとも、最も効率的なソリューションを見つけるためにさまざまなオプションを常にいじる必要がある場合ですか?


編集

私は最近、効率性の観点から、一時テーブルが関連するヒストグラム、つまり統計を持っているため、最初の良い選択であると言われました。

179
whytheq

SQLは宣言型言語であり、手続き型言語ではありません。つまり、必要な結果を記述するSQLステートメントを作成します。 SQLエンジンにhowを実行するように指示していません。

一般的なルールとして、SQLエンジンとSQLオプティマイザーに最適なクエリプランを見つけさせることをお勧めします。 SQLエンジンの開発には多くの人年の努力が費やされているため、エンジニアに方法を知ってもらいましょう。

もちろん、クエリプランが最適ではない状況もあります。次に、クエリヒントの使用、クエリの再構築、統計の更新、一時テーブルの使用、インデックスの追加などを行い、パフォーマンスを向上させます。

あなたの質問に関しては。 CTEとサブクエリのパフォーマンスは、理論的には同じである必要があります。どちらもクエリオプティマイザに同じ情報を提供するためです。 1つの違いは、1回以上使用されたCTEを簡単に識別して1回計算できることです。その後、結果を保存して複数回読み取ることができます。残念ながら、SQL Serverはこの基本的な最適化方法を利用していないようです(この一般的なサブクエリの除去と呼ぶこともあります)。

一時テーブルは別の問題です。クエリの実行方法に関する詳細なガイダンスを提供しているためです。大きな違いの1つは、オプティマイザーが一時テーブルの統計を使用してクエリプランを確立できることです。これにより、パフォーマンスが向上します。また、複数回使用される複雑なCTE(サブクエリ)がある場合、一時テーブルに格納するとパフォーマンスが向上することがよくあります。クエリは1回だけ実行されます。

あなたの質問に対する答えは、特に定期的に実行される複雑なクエリの場合、期待するパフォーマンスを得るために遊んでいる必要があるということです。理想的な世界では、クエリオプティマイザーは完璧な実行パスを見つけます。頻繁に実行されますが、パフォーマンスを向上させる方法を見つけることができる場合があります。

200
Gordon Linoff

ルールはありません。 CTEの方が読みやすいと思うので、それらを使用しますnless何らかのパフォーマンスの問題を示します。その場合、CTEが問題であると推測するのではなく、実際の問題を調査し、アプローチ。通常、この問題には、クエリで意図を宣言的に述べる方法よりも多くのことがあります。

確かに、CTEを解いたり、サブクエリを削除し、それらを#tempテーブルに置き換えて期間を短縮できる場合があります。これは、古い統計、正確な統計(テーブル値関数への参加など)を取得できないこと、並列処理、クエリの複雑さのために最適なプランを生成できないことなど、さまざまな原因が考えられます(その場合、それを分割すると、オプティマイザーに格闘のチャンスが与えられます)。ただし、#tempテーブルの作成に関係するI/Oが、CTEを使用した特定の計画形状を魅力的にしない他のパフォーマンスの側面を上回る場合もあります。

正直に言って、質問に対する「正しい」答えを提供するには変数が多すぎます。クエリがいつアプローチするかを予測する予測可能な方法はありません-理論的には、CTEまたは単一のサブクエリと同じセマンティクスshouldがまったく同じことを実行することを知っているだけです。これが真実ではない場合を提示する場合、あなたの質問はより価値があると思います-オプティマイザの制限を発見した(または既知の制限を発見した)可能性があるか、クエリが意味的に同等ではない可能性がありますまたは、最適化を妨げる要素が含まれている。

そのため、クエリを最も自然な方法で記述し、オプティマイザが抱えている実際のパフォーマンスの問題を発見した場合にのみ逸脱することをお勧めします。個人的には、それらをCTE、次にサブクエリにランク付けし、#tempテーブルが最後の手段です。

65
Aaron Bertrand

#tempは標準化され、CTEは標準化されていません。

CTEは単なる構文なので、理論上は単なるサブクエリです。実行されます。 #tempが具体化されます。したがって、何度も実行される結合の高価なCTEは、#tempの方が優れている場合があります。反対に、実行されないが数回実行される簡単な評価であれば、#tempのオーバーヘッドに値しません。

テーブル変数が気に入らないSOの人々がいますが、私はそれらがマテリアライズされており、#tempよりも作成が速いので気に入っています。クエリオプティマイザーは、テーブル変数と比較して#tempの方が優れている場合があります。

#tempまたはテーブル変数でPKを作成する機能は、CTEよりも多くの情報をクエリオプティマイザーに提供します(CTEでPKを宣言できないため)。

16
paparazzo

CTEではなく#Tempテーブルを使用することが常に望ましいと思う2つのことは次のとおりです。

  1. CTEに主キーを配置することはできません。そのため、CTEがアクセスするデータは、一時テーブルのPKまたはインデックスにアクセスするのではなく、CTEのテーブルの各インデックスを走査する必要があります。

  2. 制約、インデックス、およびプライマリキーをCTEに追加できないため、バグが入り込み、データが不正になる傾向があります。


-昨日

以下は、#table制約により、CTEの場合とは異なる不良データを防止できる例です。

DECLARE @BadData TABLE ( 
                       ThisID int
                     , ThatID int );
INSERT INTO @BadData
       ( ThisID
       , ThatID
       ) 
VALUES
       ( 1, 1 ),
       ( 1, 2 ),
       ( 2, 2 ),
       ( 1, 1 );

IF OBJECT_ID('tempdb..#This') IS NOT NULL
    DROP TABLE #This;
CREATE TABLE #This ( 
             ThisID int NOT NULL
           , ThatID int NOT NULL
                        UNIQUE(ThisID, ThatID) );
INSERT INTO #This
SELECT * FROM @BadData;
WITH This_CTE
     AS (SELECT *
           FROM @BadData)
     SELECT *
       FROM This_CTE;
11
ShanksPranks