web-dev-qa-db-ja.com

パフォーマンスが高いのは、CTEと一時テーブルのどちらですか?

CTETemporary Tablesのどちらのパフォーマンスが高いですか?

126
Blankman

私はそれらは異なる概念であると言いますが、「チョークとチーズ」と言ってもそれほど違いはありません。

  • 一時テーブルは、再利用したり、一連のデータに対して複数の処理パスを実行したりするのに適しています。

  • CTEを使用して、再帰するか、読みやすさを向上させることができます。
    そして、ビューまたはインラインテーブルの値関数のように、メインクエリで展開されるマクロのように扱うこともできます

  • 一時テーブルは、スコープに関するいくつかのルールを持つ別のテーブルです

両方を使用する(およびテーブル変数も)procを保存しました

62
gbn

場合によります。

まず第一に

共通テーブル式とは何ですか?

(非再帰的)CTEは、SQL Serverのインラインテーブル式としても使用できる他の構造と非常によく似ています。派生テーブル、ビュー、およびインラインテーブル値関数。 BOLはCTEを「一時的な結果セットと考えることができる」と述べているが、これは純粋に論理的な記述であることに注意してください。多くの場合、それ自体はマテリアル化されていません。

一時テーブルとは何ですか?

これは、tempdbのデータページに格納されている行のコレクションです。データページは、部分的または完全にメモリに常駐できます。さらに、一時テーブルにインデックスが付けられ、列統計が含まれる場合があります。

テストデータ

CREATE TABLE T(A INT IDENTITY PRIMARY KEY, B INT , F CHAR(8000) NULL);

INSERT INTO T(B)
SELECT TOP (1000000)  0 + CAST(NEWID() AS BINARY(4))
FROM master..spt_values v1,
     master..spt_values v2;

例1

WITH CTE1 AS
(
SELECT A,
       ABS(B) AS Abs_B,
       F
FROM T
)
SELECT *
FROM CTE1
WHERE A = 780

Plan 1

上記の計画では、CTE1については言及されていません。ベーステーブルに直接アクセスするだけで、同じように扱われます

SELECT A,
       ABS(B) AS Abs_B,
       F
FROM   T
WHERE  A = 780 

ここでCTEを中間一時テーブルに具体化して書き換えると、非常に非生産的です。

CTE定義の具体化

SELECT A,
       ABS(B) AS Abs_B,
       F
FROM T

約8GBのデータを一時テーブルにコピーする必要がありますが、そこから選択するオーバーヘッドも残っています。

例2

WITH CTE2
     AS (SELECT *,
                ROW_NUMBER() OVER (ORDER BY A) AS RN
         FROM   T
         WHERE  B % 100000 = 0)
SELECT *
FROM   CTE2 T1
       CROSS APPLY (SELECT TOP (1) *
                    FROM   CTE2 T2
                    WHERE  T2.A > T1.A
                    ORDER  BY T2.A) CA 

上記の例では、マシン上で約4分かかります。

1,000,000個のランダムに生成された値の15行のみが述部と一致しますが、これらを見つけるために高価なテーブルスキャンが16回発生します。

enter image description here

これは、中間結果を具体化するための良い候補です。同等の一時テーブルの書き換えには25秒かかりました。

INSERT INTO #T
SELECT *,
       ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM   T
WHERE  B % 100000 = 0

SELECT *
FROM   #T T1
       CROSS APPLY (SELECT TOP (1) *
                    FROM   #T T2
                    WHERE  T2.A > T1.A
                    ORDER  BY T2.A) CA 

With Plan

一時的なテーブルへのクエリの一部の中間マテリアライゼーションは、マテリアライズ結果の統計を利用してクエリの残りの部分を再コンパイルできる場合に、一度しか評価されない場合でも役立つことがあります。このアプローチの例は、SQL Catの記事 複雑なクエリを分解するタイミング にあります。

状況によっては、SQL Serverはスプールを使用して中間結果をキャッシュします。 CTEのサブツリーを再評価する必要はありません。これは、(移行された)接続項目で説明されています CTEまたは派生テーブルの中間マテリアライゼーションを強制するヒントを提供します 。ただし、これに関する統計は作成されず、スプールされた行の数が推定値と大きく異なる場合でも、進行中の実行プランが応答して動的に適応することはできません(少なくとも現在のバージョンでは。未来)。

162
Martin Smith

CTEには用途があります-CTEのデータが小さく、再帰テーブルの場合と同様に読みやすさが大幅に向上している場合。ただし、そのパフォーマンスは確かにテーブル変数よりも優れているわけではなく、非常に大きなテーブルを扱う場合、一時テーブルはCTEを大幅に上回ります。これは、CTEでインデックスを定義できず、別のテーブルとの結合が必要な大量のデータがある場合(CTEは単にマクロのようなものだからです)。それぞれが数百万行のレコードを持つ複数のテーブルを結合している場合、CTEは一時テーブルよりもパフォーマンスが著しく低下します。

47
CSW

一時テーブルは常にディスク上にあります-したがって、CTEをメモリに保持できる限り、(テーブル変数のように)より高速になります。

ただし、CTE(または一時テーブル変数)のデータ負荷が大きくなりすぎると、ディスクにも保存されるため、大きなメリットはありません。

一般的に、CTEは使用後に消えてしまうため、一時テーブルよりもCTEを好みます。明示的に削除したり、何かを削除したりする必要はありません。

したがって、最終的には明確な答えはありませんが、個人的には、一時テーブルよりもCTEを好むでしょう。

32
marc_s

CTEは物理的なスペースを取りません。これは、結合を使用できる結果セットにすぎません。

一時テーブルは一時的なものです。すべての変数を定義する必要があるため、インデックスを作成し、通常のテーブルのような制約を作成できます。

セッション内の一時テーブルのスコープのみ。例:2つのSQLクエリウィンドウを開く

create table #temp(empid int,empname varchar)
insert into #temp 
select 101,'xxx'

select * from #temp

このクエリを最初のウィンドウで実行してから、次のクエリを2番目のウィンドウで実行すると、違いがわかります。

select * from #temp
6
selvaraj

そのため、最適化するために割り当てられたクエリは、SQLサーバーの2つのCTEで記述されました。 28秒かかっていました。

それらを一時テーブルに変換するのに2分かかり、クエリには3秒かかりました

結合されているフィールドの一時テーブルにインデックスを追加し、2秒に短縮しました

3分間の作業で、CTEを削除することで12倍高速になりました。私も個人的にはデバッグが難しいCTEを使用しません。

おかしなことは、CTEは両方とも一度しか使用されなかったのに、インデックスを付けることで50%速くなったことです。

5
Mark Monforti

私は両方を使用しましたが、大規模で複雑な手順では、一時テーブルの方が作業しやすく、体系的です。 CTEには用途がありますが、一般に小さなデータが使用されます。

たとえば、15秒で大規模な計算の結果を返すsprocを作成しましたが、このコードをCTEで実行するように変換し、同じ結果を達成するために8分を超える実行を確認しました。

4
Andy_RC

CTEの優れたパフォーマンスが得られた1つの用途は、比較的複雑なクエリを、それぞれ数百万行の数個のテーブルに結合する必要がある場合でした。

CTEを使用して、最初にインデックス列に基づいてサブセットを選択し、最初にこれらのテーブルをそれぞれ関連する数千行に切り捨ててから、CTEをメインクエリに結合しました。これにより、クエリの実行時間が急激に短縮されました。

CTEの結果はキャッシュされず、テーブル変数がより良い選択だったかもしれませんが、実際に試してみて、上記のシナリオに合うものを見つけました。

3
purchas

パーティーに遅れて、しかし...

私が働いている環境は非常に制約されており、一部のベンダー製品をサポートし、レポートなどの「付加価値」サービスを提供しています。ポリシーと契約の制限により、通常、個別のテーブル/データスペースの贅沢や永続的なコードを作成することは許可されていません[アプリケーションによって多少改善されます]。

IOW、I できません通常、ストアドプロシージャやUDFまたは一時テーブルなどを開発します。 w/in CRなど)。小さな節約の恩恵の1つは、Crystalでコマンド(およびSQL式)を使用できることです。通常のテーブルの追加/リンク機能では効率的でないいくつかのことは、SQLコマンドを定義することで実行できます。私はそれを通してCTEを使用し、「リモート」で非常に良い結果を得ました。また、CTEはレポートのメンテナンスにも役立ちます。コードを開発する必要はなく、DBAに渡してコンパイル、暗号化、転送、インストールを行い、複数レベルのテストが必要です。ローカルインターフェイスを介してCTEを実行できます。

CRを使用したCTEを使用することの欠点は、各レポートが個別であることです。レポートごとに各CTEを維持する必要があります。 SPとUDFを実行できる場合は、複数のレポートで使用できるものを開発できます。SPへのリンクと、通常のテーブルで作業しているかのようにパラメーターを渡すだけです。 CRは、SQLコマンドへのパラメーターの処理があまり得意ではないため、CR/CTEの側面が欠けている場合があります。そのような場合、私は通常、CTEを定義して十分なデータ(ただし、すべてのデータではない)を返し、CRのレコード選択機能を使用してそれをスライスおよびダイスします。

だから...私の投票はCTEに対するものです(データ領域が得られるまで)。

3
Marc

これは本当に開かれた質問であり、その使用方法と一時テーブルのタイプ(テーブル変数または従来のテーブル)にすべて依存します。

従来の一時テーブルはデータを一時DBに格納しますが、一時テーブルの速度は低下します。ただし、テーブル変数はそうではありません。

1
JoshBerke

SQL Serverでの経験から、CTEが一時テーブルを上回るシナリオの1つを見つけました

ストアドプロシージャで一度だけ、複雑なクエリからDataSet(〜100000)を使用する必要がありました。

  • 一時テーブルは、プロシージャの実行速度が遅いSQLでオーバーヘッドを引き起こしていました(一時テーブルはtempdbに存在する実際のマテリアライズテーブルであり、現在のプロシージャが存続する限り持続します)

  • 一方、CTEでは、CTEは次のクエリが実行されるまでのみ持続します。したがって、CTEはスコープが制限された便利なメモリ内構造です。 CTEはデフォルトでtempdbを使用しません。

これは、CTEがコードの単純化と一時テーブルの性能向上に本当に役立つ1つのシナリオです。私は2つのCTEを使用していました

WITH CTE1(ID, Name, Display) 
AS (SELECT ID,Name,Display from Table1 where <Some Condition>),
CTE2(ID,Name,<col3>) AS (SELECT ID, Name,<> FROM CTE1 INNER JOIN Table2 <Some Condition>)
SELECT CTE2.ID,CTE2.<col3>
FROM CTE2
GO
1
Amardeep Kohli

これをテストしました。CTEと非CTE(両方のユニオンインスタンスに対してクエリが入力された)の両方で、約31秒かかりました。ただし、CTEによりコードはさらに読みやすくなり、241行から130行に削減されました。これは非常に素晴らしいことです。一方、一時テーブルは132行に削減し、5秒で実行しました。冗談抜き。このテストはすべてキャッシュされていました。以前はクエリはすべて複数回実行されていました。

1
user2989981