SQL Serverの一時テーブルとテーブル変数の違いは何ですか?


452

これは非常に多くの神話と相反する見解を持つ領域のようです。

それでは、SQL Serverのテーブル変数とローカル一時テーブルの違いは何ですか?


回答:


668

内容

内容

警告

この回答では、SQL Server 2000で導入された「クラシック」テーブル変数について説明します。メモリOLTPのSQL Server 2014では、メモリ最適化テーブルタイプが導入されます。それらのテーブル変数インスタンスは、以下で説明するものと多くの点で異なります!(詳細)。

ストレージの場所

変わりはない。両方ともに保存されtempdbます。

テーブル変数の場合、これは常に当てはまるわけではないが、以下から確認できることが示唆されています。

DECLARE @T TABLE(X INT)

INSERT INTO @T VALUES(1),(2)

SELECT sys.fn_PhysLocFormatter(%%physloc%%) AS [File:Page:Slot]
FROM @T

結果の例(tempdb2行の場所が表示されます)

File:Page:Slot
----------------
(1:148:0)
(1:148:1)

論理的な場所

@table_variables#tempテーブルよりも現在のデータベースの一部であるかのように振る舞います。テーブル変数(2005年以降)の列照合は、明示的に指定されていない場合、現在のデータベースの#temp照合順序になりますが、テーブルの場合、デフォルト照合順序tempdb詳細)が使用されます。さらに、#tempテーブルに使用するユーザー定義のデータ型とXMLコレクションはtempdbになければなりませんが、テーブル変数は現在のデータベース(ソース)からそれらを使用できます。

SQL Server 2012では、包含データベースが導入されています。これらの一時テーブルの動作は異なります(h / tアーロン)

包含データベースでは、一時テーブルのデータは包含データベースの照合で照合されます。

  • 一時テーブルに関連付けられているすべてのメタデータ(テーブルと列の名前、インデックスなど)はカタログ照合に含まれます。
  • 名前付き制約は、一時テーブルでは使用できません。
  • 一時テーブルは、ユーザー定義型、XMLスキーマコレクション、またはユーザー定義関数を参照できません。

異なるスコープへの可視性

@table_variables宣言されているバッチとスコープ内でのみアクセスできます。#temp_tables子バッチ内でアクセス可能です(ネストされたトリガー、プロシージャー、exec呼び出し)。#temp_tables外部スコープ(@@NESTLEVEL=0)で作成されたセッションは、セッションが終了するまで持続するため、バッチにまたがることができます。ただし、次に説明するように、どちらのタイプのオブジェクトも子バッチで作成し、呼び出しスコープでアクセスすることはできません(グローバル##tempテーブルで可能です)。

一生

@table_variablesDECLARE @.. TABLEステートメントを含むバッチが実行されると(そのバッチのユーザーコードが実行される前に)暗黙的に作成され、最後に暗黙的にドロップされます。

パーサーでは、DECLAREステートメントの前にテーブル変数を試して使用することはできませんが、暗黙的な作成を以下に示します。

IF (1 = 0)
BEGIN
DECLARE @T TABLE(X INT)
END

--Works fine
SELECT *
FROM @T

#temp_tablesTSQL CREATE TABLEステートメントが検出されたときに明示的に作成され、明示的にドロップできるDROP TABLEか、バッチが終了したときに暗黙的にドロップされます(で子バッチで作成された場合@@NESTLEVEL > 0)またはそうでない場合はセッションが終了します。

NBは:ストアドルーチン内のオブジェクトの両方のタイプをキャッシュすることができ、繰り返し作成し、新しいテーブルを落とすのではなく。以下のために違反する可能性がある。このキャッシュはしかし起こることができるときには制限があります#temp_tablesが、上の制約がどの@table_variablesとにかく防ぐには。ここに示すように、キャッシュ#tempテーブルのメンテナンスオーバーヘッドは、テーブル変数の場合よりもわずかに大きくなります

オブジェクトのメタデータ

これは、両方のタイプのオブジェクトで本質的に同じです。のシステムベーステーブルに保存されtempdbます。#tempただしOBJECT_ID('tempdb..#T')、システムテーブルにキー入力するために使用できるため、テーブル を確認する方が簡単です。また、内部で生成された名前は、CREATE TABLEステートメントで定義された名前により密接に関連付けられます。テーブル変数の場合、object_id関数は機能せず、内部名はシステムによって完全に生成され、変数名とは関係ありません。以下は、メタデータがまだ存在することを示しています(ただし、一意であることが望ましい)列名を入力します。一意の列名を持たないテーブルの場合、object_id DBCC PAGEは空でない限り使用して決定できます。

/*Declare a table variable with some unusual options.*/
DECLARE @T TABLE
(
[dba.se] INT IDENTITY PRIMARY KEY NONCLUSTERED,
A INT CHECK (A > 0),
B INT DEFAULT 1,
InRowFiller char(1000) DEFAULT REPLICATE('A',1000),
OffRowFiller varchar(8000) DEFAULT REPLICATE('B',8000),
LOBFiller varchar(max) DEFAULT REPLICATE(cast('C' as varchar(max)),10000),
UNIQUE CLUSTERED (A,B) 
    WITH (FILLFACTOR = 80, 
         IGNORE_DUP_KEY = ON, 
         DATA_COMPRESSION = PAGE, 
         ALLOW_ROW_LOCKS=ON, 
         ALLOW_PAGE_LOCKS=ON)
)

INSERT INTO @T (A)
VALUES (1),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13)

SELECT t.object_id,
       t.name,
       p.rows,
       a.type_desc,
       a.total_pages,
       a.used_pages,
       a.data_pages,
       p.data_compression_desc
FROM   tempdb.sys.partitions AS p
       INNER JOIN tempdb.sys.system_internals_allocation_units AS a
         ON p.hobt_id = a.container_id
       INNER JOIN tempdb.sys.tables AS t
         ON t.object_id = p.object_id
       INNER JOIN tempdb.sys.columns AS c
         ON c.object_id = p.object_id
WHERE  c.name = 'dba.se'

出力

Duplicate key was ignored.

 +-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| object_id |   name    | rows |     type_desc     | total_pages | used_pages | data_pages | data_compression_desc |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | PAGE                  |
| 574625090 | #22401542 |   13 | LOB_DATA          |          24 |         19 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | ROW_OVERFLOW_DATA |          16 |         14 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | NONE                  |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+

取引

上の操作@table_variablesは、外部ユーザートランザクションとは無関係にシステムトランザクションとして実行されますが、同等の#tempテーブル操作はユーザートランザクション自体の一部として実行されます。このため、ROLLBACKコマンドは#tempテーブルに影響しますが、@table_variableそのまま残ります。

DECLARE @T TABLE(X INT)
CREATE TABLE #T(X INT)

BEGIN TRAN

INSERT #T
OUTPUT INSERTED.X INTO @T
VALUES(1),(2),(3)

/*Both have 3 rows*/
SELECT * FROM #T
SELECT * FROM @T

ROLLBACK

/*Only table variable now has rows*/
SELECT * FROM #T
SELECT * FROM @T
DROP TABLE #T

ロギング

どちらもtempdbトランザクションログにログレコードを生成します。一般的な誤解は、これはテーブル変数の場合ではないため、これを示すスクリプトは以下のとおりです。テーブル変数を宣言し、いくつかの行を追加してから更新および削除します。

テーブル変数はバッチの開始時と終了時に暗黙的に作成および削除されるため、完全なログを表示するには複数のバッチを使用する必要があります。

USE tempdb;

/*
Don't run this on a busy server.
Ideally should be no concurrent activity at all
*/
CHECKPOINT;

GO

/*
The 2nd column is binary to allow easier correlation with log output shown later*/
DECLARE @T TABLE ([C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3] INT, B BINARY(10))

INSERT INTO @T
VALUES (1, 0x41414141414141414141), 
       (2, 0x41414141414141414141)

UPDATE @T
SET    B = 0x42424242424242424242

DELETE FROM @T

/*Put allocation_unit_id into CONTEXT_INFO to access in next batch*/
DECLARE @allocId BIGINT, @Context_Info VARBINARY(128)

SELECT @Context_Info = allocation_unit_id,
       @allocId = a.allocation_unit_id 
FROM   sys.system_internals_allocation_units a
       INNER JOIN sys.partitions p
         ON p.hobt_id = a.container_id
       INNER JOIN sys.columns c
         ON c.object_id = p.object_id
WHERE  ( c.name = 'C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3' )

SET CONTEXT_INFO @Context_Info

/*Check log for records related to modifications of table variable itself*/
SELECT Operation,
       Context,
       AllocUnitName,
       [RowLog Contents 0],
       [Log Record Length]
FROM   fn_dblog(NULL, NULL)
WHERE  AllocUnitId = @allocId

GO

/*Check total log usage including updates against system tables*/
DECLARE @allocId BIGINT = CAST(CONTEXT_INFO() AS BINARY(8));

WITH T
     AS (SELECT Operation,
                Context,
                CASE
                  WHEN AllocUnitId = @allocId THEN 'Table Variable'
                  WHEN AllocUnitName LIKE 'sys.%' THEN 'System Base Table'
                  ELSE AllocUnitName
                END AS AllocUnitName,
                [Log Record Length]
         FROM   fn_dblog(NULL, NULL) AS D)
SELECT Operation = CASE
                     WHEN GROUPING(Operation) = 1 THEN 'Total'
                     ELSE Operation
                   END,
       Context,
       AllocUnitName,
       [Size in Bytes] = COALESCE(SUM([Log Record Length]), 0),
       Cnt = COUNT(*)
FROM   T
GROUP  BY GROUPING SETS( ( Operation, Context, AllocUnitName ), ( ) )
ORDER  BY GROUPING(Operation),
          AllocUnitName 

返却値

詳細ビュー

結果のスクリーンショット

概要ビュー(暗黙的なドロップおよびシステムベーステーブルのロギングを含む)

結果のスクリーンショット

私が両方の操作を識別できる限り、ほぼ同じ量のロギングが生成されます。

ロギングのは非常に似ていますが、1つの重要な違いは、#tempユーザートランザクションを含むものが終了するまでテーブルに関連するログレコードを消去できないため、ある時点で#tempテーブルに書き込む長時間実行トランザクションはログの切り捨てを防ぎますtempdbが、自律型トランザクションはテーブル変数のために生成されません。

テーブル変数はサポートしTRUNCATEていないため、テーブルからすべての行を削除することが要件である場合、ロギングのデメリットになる可能性があります(ただし、非常に小さなテーブルの場合DELETE はいずれにせようまくいきます

カーディナリティ

テーブル変数を含む実行計画の多くは、それらからの出力として推定される単一の行を表示します。テーブル変数のプロパティを調べると、SQL Serverがテーブル変数の行がゼロであると認識していることがわかります(ゼロ行のテーブルから1行が出力されると推定される理由は、@ Paul Whiteによって説明されています)。

ただし、前のセクションで示した結果は、で正確なrowsカウントを示していますsys.partitions。問題は、ほとんどの場合、テーブルが空である間にテーブル変数を参照するステートメントがコンパイルされることです。ステートメントが@table_variable移入された後にステートメントが(再)コンパイルされる場合、これは代わりにテーブルのカーディナリティに使用されます(これは、明示的recompileまたはおそらくステートメントが遅延コンパイルまたは再コンパイルを引き起こす別のオブジェクトを参照するために発生する可能性があります)

DECLARE @T TABLE(I INT);

INSERT INTO @T VALUES(1),(2),(3),(4),(5)

CREATE TABLE #T(I INT)

/*Reference to #T means this statement is subject to deferred compile*/
SELECT * FROM @T WHERE NOT EXISTS(SELECT * FROM #T)

DROP TABLE #T

計画は、遅延コンパイル後の正確な推定行数を示します。

正確な行数を表示します

SQL Server 2012 SP2では、トレースフラグ2453が導入されています。詳細については、「リレーショナルエンジン」を参照してください

このトレースフラグを有効にすると、自動再コンパイルが発生するカーディナリティが考慮されるようになります。

注意:互換性レベル150のAzureでは、ステートメントのコンパイルは最初の実行まで延期されます。これは、ゼロ行の推定問題の影響を受けなくなることを意味します。

列統計なし

ただし、テーブルのカーディナリティがより正確であっても、推定行数がより正確になるわけではありません(テーブル内のすべての行で操作を実行する場合を除く)。SQL Serverはテーブル変数の列統計をまったく維持しないため、比較述語に基づいた推測に基づいてフォールバックします(たとえば、テーブルの10%が=一意でない列に対して返され、30%が>比較に対して返されます)。対照的に、テーブルの列統計維持され#tempます。

SQL Serverは、各列に加えられた変更の数のカウントを保持します。計画がコンパイルされてからの変更の数が再コンパイルのしきい値(RT)を超える場合、計画は再コンパイルされ、統計が更新されます。RTはテーブルのタイプとサイズに依存します。

SQL Server 2008のプランキャッシュから

RTは次のように計算されます。(nは、クエリプランがコンパイルされるときのテーブルのカーディナリティを指します。)

永久表
-n <= 500の場合、RT =500。
-n> 500の場合、RT = 500 + 0.20 * n。

一時テーブル
-n <6の場合、RT = 6-6
<= n <= 500の場合、RT =500。
-n> 500の場合、RT = 500 + 0.20 * n。
テーブル変数
-RTは存在しません。したがって、テーブル変数のカーディナリティが変更されているため、再コンパイルは行われません。 (ただし、以下のTF 2453に関する注意を参照してください)

KEEP PLANヒントは、室温を設定するために使用することができる#temp永続テーブルと同じテーブル。

このすべての最終的な効果は、#tempテーブルに対して生成される実行プラン@table_variablesは、SQL Serverがより適切な情報を処理するため、多くの行が含まれる場合よりも桁違いに優れていることです。

NB1:テーブル変数には統計はありませんが、トレースフラグ2453の下で「統計変更」再コンパイルイベントが発生する可能性があります(「単純な」プランには適用されません)。追加の場合N=0 -> RT = 1。つまり、テーブル変数が空のときにコンパイルされたすべてのステートメントは、空でないTableCardinalityときに最初に実行されたときに再コンパイルおよび修正されます。コンパイル時テーブルのカーディナリティはプランに格納され、同じカーディナリティでステートメントが再度実行された場合(制御ステートメントのフローまたはキャッシュされたプランの再利用のいずれか)、再コンパイルは発生しません。

NB2:ストアドプロシージャのキャッシュされた一時テーブルの場合、再コンパイルのストーリーは上記の説明よりもはるかに複雑です。参照してください。ストアドプロシージャ内の一時テーブルのすべての血みどろの詳細については、を。

再コンパイル

同様に、上述の変形ベースの再コンパイル#tempテーブルも関連付けることができる追加のコンパイル彼らはコンパイル(例えばDDL変更をトリガするテーブル変数のために禁止されている操作を許可単にのでCREATE INDEXALTER TABLE

ロッキング

述べられているテーブル変数は、ロックに参加しないこと。これはそうではありません。以下を実行すると、SSMSメッセージタブに、挿入ステートメントで取得および解放されたロックの詳細が出力されます。

DECLARE @tv_target TABLE (c11 int, c22 char(100))

DBCC TRACEON(1200,-1,3604)

INSERT INTO @tv_target (c11, c22)

VALUES (1, REPLICATE('A',100)), (2, REPLICATE('A',100))

DBCC TRACEOFF(1200,-1,3604)

SELECTテーブル変数からのクエリの場合、Paul Whiteは、暗黙的にNOLOCKヒントが自動的に付けられることをコメントで指摘しています。これを以下に示します

DECLARE @T TABLE(X INT); 

SELECT X
FROM @T 
OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8607)

出力

*** Output Tree: (trivial plan) ***

        PhyOp_TableScan TBL: @T Bmk ( Bmk1000) IsRow: COL: IsBaseRow1002  Hints( NOLOCK )

ただし、これがロックに与える影響はごくわずかです。

SET NOCOUNT ON;

CREATE TABLE #T( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))


DECLARE @T TABLE ( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))

DECLARE @I INT = 0

WHILE (@I < 10000)
BEGIN
INSERT INTO #T DEFAULT VALUES
INSERT INTO @T DEFAULT VALUES
SET @I += 1
END

/*Run once so compilation output doesn't appear in lock output*/
EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEON(1200,3604,-1)
SELECT *, sys.fn_PhysLocFormatter(%%physloc%%)
FROM @T

PRINT '--*--'

EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEOFF(1200,3604,-1)

DROP TABLE #T

これらのどちらも、SQL Server が両方に対して割り当て順序スキャンを使用したことを示すインデックスキーの順序になりません。

上記のスクリプトを2回実行し、2回目の実行の結果は以下のとおりです。

Process 58 acquiring Sch-S lock on OBJECT: 2:-1325894110:0  (class bit0 ref1) result: OK

--*--
Process 58 acquiring IS lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 acquiring S lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 releasing lock on OBJECT: 2:-1293893996:0 

SQL Serverはオブジェクトのスキーマ安定性ロックを取得するだけなので、テーブル変数のロック出力は非常に最小限です。しかし、#tempテーブルの場合は、オブジェクトレベルのSロックを解除するという点でほぼ同じです。NOLOCKヒントまたはREAD UNCOMMITTEDで作業するとき分離レベルは、もちろん明示的に指定することができる#tempだけでなくテーブル。

周囲のユーザートランザクションのログに関する問題と同様に、#tempテーブルのロックが長く保持されることを意味します。以下のスクリプトで

    --BEGIN TRAN;   

    CREATE TABLE #T (X INT,Y CHAR(4000) NULL);

    INSERT INTO #T (X) VALUES(1) 

    SELECT CASE resource_type
             WHEN  'OBJECT' THEN OBJECT_NAME(resource_associated_entity_id, 2)
             WHEN  'ALLOCATION_UNIT' THEN (SELECT OBJECT_NAME(object_id, 2)
                                           FROM  tempdb.sys.allocation_units a 
                                           JOIN tempdb.sys.partitions p ON a.container_id = p.hobt_id
                                           WHERE  a.allocation_unit_id = resource_associated_entity_id)
             WHEN 'DATABASE' THEN DB_NAME(resource_database_id)                                      
             ELSE (SELECT OBJECT_NAME(object_id, 2)
                   FROM   tempdb.sys.partitions
                   WHERE  partition_id = resource_associated_entity_id)
           END AS object_name,
           *
    FROM   sys.dm_tran_locks
    WHERE  request_session_id = @@SPID

    DROP TABLE #T

   -- ROLLBACK  

どちらの場合も明示的なユーザートランザクションの外部で実行された場合、チェック時に返されるロックsys.dm_tran_locksはの共有ロックのみDATABASEです。

コメントを解除すると、BEGIN TRAN ... ROLLBACK26行が返され、オブジェクト自体とシステムテーブル行の両方でロックが保持され、ロールバックが可能になり、他のトランザクションがコミットされていないデータを読み取れないようになります。同等のテーブル変数操作は、ユーザートランザクションによるロールバックの対象ではなく、次のステートメントでチェックするためにこれらのロックを保持する必要はありませんが、プロファイラーで取得およびリリースされたロックをトレースするか、トレースフラグ1200を使用すると、多くのロックイベントがまだ表示されます発生する。

インデックス

SQL Server 2014より前のバージョンでは、一意の制約または主キーを追加する副作用として、テーブル変数にのみ暗黙的にインデックスを作成できます。これはもちろん、一意のインデックスのみがサポートされることを意味します。独自のクラスタ化インデックスを持つテーブル上の非ユニークな非クラスタ化インデックスは、単にそれを宣言することがシミュレートすることができますUNIQUE NONCLUSTERED(SQL Serverがなり、希望NCIキーの最後にCIキーを追加し、とにかく舞台裏でこれを行うにも非ユニーク場合NCIを指定できます)

実証され、以前の様々なとしてindex_optionsが含む制約宣言で指定することができDATA_COMPRESSIONIGNORE_DUP_KEYFILLFACTOR(それが唯一のインデックス上の任意の差が再構築になるだろうとあなたは、テーブル変数にインデックスを再構築することができないように、その1を設定するにはポイントがないのに!)

さらに、テーブル変数はINCLUDEd列、フィルター選択されたインデックス(2016年まで)、またはパーティション分割をサポートしていませ#tempん(テーブルでパーティション分割スキームを作成する必要がありますtempdb)。

SQL Server 2014のインデックス

非一意インデックスは、SQL Server 2014のテーブル変数定義でインラインで宣言できます。これの構文の例を以下に示します。

DECLARE @T TABLE (
C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/
C2 INT INDEX IX2 NONCLUSTERED,
       INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/
);

SQL Server 2016のインデックス

CTP 3.1から、テーブル変数のフィルター選択されたインデックスを宣言できるようになりました。RTMによってそれがあり、彼らはいえ列も許可されている場合が含ま可能性が高いため、リソースの制約にSQL16にそれをすることはありません

DECLARE @T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)

平行度

(または変更)に挿入クエリが@table_variables並列プランを持つことができない、#temp_tablesこのように限定されるものではありません。

次のように書き直すことでSELECTパーツを並行して実行できるようになりますが、最終的には(舞台裏で)隠された一時テーブルを使用するという明らかな回避策があります。

INSERT INTO @DATA ( ... ) 
EXEC('SELECT .. FROM ...')

ここでの私の答えに示されているように、テーブル変数から選択するクエリにはそのような制限はありません

その他の機能の違い

  • #temp_tables関数内で使用することはできません。@table_variablesスカラーまたはマルチステートメントテーブルUDF内で使用できます。
  • @table_variables 名前付き制約を持つことはできません。
  • @table_variablesすることはできませんSELECT-ed INTOALTER、-ed TRUNCATEDまたはの対象となるDBCCようなコマンドDBCC CHECKIDENTやのSET IDENTITY INSERTやなどのテーブルヒントをサポートしていません。WITH (FORCESCAN)
  • CHECK テーブル変数の制約は、単純化、暗黙の述部、または矛盾検出のためにオプティマイザーによって考慮されません。
  • テーブル変数は、行セット共有の最適化に適していないようです。つまり、これらに対する変数の削除と更新のプランは、より多くのオーバーヘッドとPAGELATCH_EX待機に直面する可能性があります。(

メモリのみ?

冒頭で述べたように、両方とものページに保存されますtempdb。ただし、これらのページをディスクに書き込む際の動作に違いがあるかどうかについては触れませんでした。

私は今、これについて少量のテストを行ってきましたが、今のところそのような違いは見られません。SQL Server 250ページのインスタンスで行った特定のテストでは、データファイルが書き込まれる前のカットオフポイントのようです。

注意:以下の動作は、SQL Server 2014またはSQL Server 2012 SP1 / CU10またはSP2 / CU1では発生しなくなりました。SQL Server 2014でのその変更の詳細:tempdb Hidden Performance Gem

以下のスクリプトを実行する

CREATE TABLE #T(X INT, Filler char(8000) NULL)
INSERT INTO #T(X)
SELECT TOP 250 ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM master..spt_values
DROP TABLE #T

またtempdb、Process Monitorを使用したデータファイルへの書き込みの監視では、何も表示されませんでした(オフセット73,728でのデータベースブートページへの書き込みを除くことがあります)。に変更250した後、251私は以下のように書き込みを見始めました。

ProcMon

上記のスクリーンショットは、5 * 32ページの書き込みと、161ページがディスクに書き込まれたことを示す1ページの書き込みを示しています。テーブル変数を使用したテストでも、250ページという同じカットオフポイントを取得しました。以下のスクリプトは、別の方法を示しています。sys.dm_os_buffer_descriptors

DECLARE @T TABLE (
  X        INT,
  [dba.se] CHAR(8000) NULL)

INSERT INTO @T
            (X)
SELECT TOP 251 Row_number() OVER (ORDER BY (SELECT 0))
FROM   master..spt_values

SELECT is_modified,
       Count(*) AS page_count
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = (SELECT a.allocation_unit_id
                                 FROM   tempdb.sys.partitions AS p
                               INNER JOIN tempdb.sys.system_internals_allocation_units AS a
                                          ON p.hobt_id = a.container_id
                                        INNER JOIN tempdb.sys.columns AS c
                                          ON c.object_id = p.object_id
                                 WHERE  c.name = 'dba.se')
GROUP  BY is_modified 

結果

is_modified page_count
----------- -----------
0           192
1           61

192ページがディスクに書き込まれ、ダーティフラグがクリアされたことを示しています。また、ディスクに書き込まれたからといって、ページがバッファプールからすぐに削除されるわけではないことも示しています。このテーブル変数に対するクエリは、メモリから完全に満たすことができます。

約1,843,000 KB(約23,000ページ)として割り当てられたバッファプールページにmax server memory設定2000 MBおよびDBCC MEMORYSTATUS報告するアイドルサーバーで、1,000行/ページのバッチで上記のテーブルに挿入し、各反復について記録しました。

SELECT Count(*)
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = @allocId
       AND page_type = 'DATA_PAGE' 

テーブル変数とテーブルの両方#tempがほぼ同一のグラフを提供し、それらがメモリに完全に保持されていない点に到達する前にバッファプールをほぼ最大限に管理したため、メモリの量に特別な制限はないようですどちらも消費できます。

バッファプール内のページ


SQL Serverは、同等のテーブル変数と比較して、一時テーブルを作成するときに(キャッシュを使用する場合でも)かなり多くのラッチを取得することがわかりました。テストするには、latch_acquiredデバッグXEを使用し、約35列程度のテーブルを作成します。テーブル変数は4つのラッチを取得し、一時テーブルは約70のラッチを取得することがわかりました。
ジョーオブビッシュ

40

勉強よりも特定の経験に基づいて指摘したいことがいくつかあります。DBAとして、私は非常に新しいので、必要に応じて修正してください。

  1. #tempテーブルは、デフォルトでSQL Serverインスタンスのデフォルトの照合を使用します。したがって、特に指定しない限り、masterdbの照合順序がデータベースと異なる場合、#tempテーブルとデータベーステーブルの間で値を比較または更新する際に問題が発生する可能性があります。参照:http : //www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collat​​ion/
  2. 完全に個人的な経験に基づいて、利用可能なメモリはパフォーマンスを向上させる効果があるようです。MSDNは、より小さな結果セットを格納するためにテーブル変数を使用することを推奨していますが、ほとんどの場合、違いは目立ちません。ただし、大規模なセットでは、テーブル変数の方がはるかに多くのメモリを消費し、クエリのクロール速度が低下する可能性があることが明らかになります。

6
また、SQL Server 2012を使用していて、データベースが含まれている場合、#tempテーブルの照合は呼び出し元データベースの照合を継承できることに注意してください。
アーロンバートランド

スモールセットとラージセットの#2の説明stackoverflow.com/a/14465163/5224021
GibralterTop
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.