Was ist der Unterschied zwischen einer temporären Tabelle und einer Tabellenvariablen in SQL Server?

Antworten:

668

Inhalt

Inhalt

Vorbehalt

In dieser Antwort werden die in SQL Server 2000 eingeführten "klassischen" Tabellenvariablen erläutert. SQL Server 2014 im Speicher OLTP führt speicheroptimierte Tabellentypen ein. Tabellenvariableninstanzen davon unterscheiden sich in vielerlei Hinsicht von den unten diskutierten! ( weitere Details ).

Lagerraum

Kein Unterschied. Beide sind in gespeichert tempdb.

Ich habe gesehen, dass dies für Tabellenvariablen nicht immer der Fall ist, dies kann jedoch anhand der folgenden Tabelle überprüft werden

DECLARE @T TABLE(X INT)

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

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

Beispielergebnisse (Anzeige der Position in tempdbden 2 Zeilen werden gespeichert)

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

Logischer Ort

@table_variablesVerhalten Sie sich mehr als ob sie Teil der aktuellen Datenbank wären als #tempTabellen. Für Spaltenkollatierungen von Tabellenvariablen (seit 2005) gilt, wenn nicht explizit angegeben, die der aktuellen Datenbank, während für #tempTabellen die Standardkollatierung von tempdb( Weitere Details ) verwendet wird. Außerdem müssen benutzerdefinierte Datentypen und XML-Auflistungen in tempdb vorliegen, damit sie für #tempTabellen verwendet werden können. Tabellenvariablen können sie jedoch aus der aktuellen Datenbank ( Quelle ) verwenden.

SQL Server 2012 führt enthaltene Datenbanken ein. das Verhalten von temporären Tabellen in diesen unterscheidet sich (h / t Aaron)

In einer enthaltenen Datenbank werden temporäre Tabellendaten in der Kollation der enthaltenen Datenbank gesammelt.

  • Alle mit temporären Tabellen verknüpften Metadaten (z. B. Tabellen- und Spaltennamen, Indizes usw.) befinden sich in der Katalogsortierung.
  • Benannte Einschränkungen dürfen in temporären Tabellen nicht verwendet werden.
  • Temporäre Tabellen verweisen möglicherweise nicht auf benutzerdefinierte Typen, XML-Schemasammlungen oder benutzerdefinierte Funktionen.

Sichtbarkeit für verschiedene Bereiche

@table_variablesDer Zugriff ist nur innerhalb des Stapels und Bereichs möglich, in dem sie deklariert sind. #temp_tablessind in untergeordneten Stapeln zugänglich (verschachtelte Trigger, Prozeduren, execAufrufe). #temp_tablesAm äußeren Gültigkeitsbereich ( @@NESTLEVEL=0) erstellte Objekte können sich auch über mehrere Stapel erstrecken, da sie bis zum Ende der Sitzung bestehen bleiben. Keiner der Objekttypen kann in einem untergeordneten Stapel erstellt werden und im aufrufenden Bereich aufgerufen werden, wie im Folgenden erläutert (es können jedoch auch globale ##tempTabellen verwendet werden).

Lebenszeit

@table_variableswerden implizit erstellt, wenn ein Stapel mit einer DECLARE @.. TABLEAnweisung ausgeführt wird (bevor ein Benutzercode in diesem Stapel ausgeführt wird) und am Ende implizit gelöscht.

Obwohl der Parser nicht zulässt, dass Sie versuchen, die Tabellenvariable vor der DECLAREAnweisung zu verwenden, wird die implizite Erstellung unten angezeigt.

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

--Works fine
SELECT *
FROM @T

#temp_tableswerden explizit erstellt, wenn die TSQL- CREATE TABLEAnweisung angetroffen wird, und können explizit mit gelöscht werden DROP TABLEoder werden implizit gelöscht, wenn der Stapel endet (wenn sie in einem untergeordneten Stapel mit erstellt wurden @@NESTLEVEL > 0) oder wenn die Sitzung andernfalls endet.

Hinweis: In gespeicherten Routinen können beide Objekttypen zwischengespeichert werden, anstatt wiederholt neue Tabellen zu erstellen und zu löschen. Es gibt jedoch Einschränkungen, wann dieses Caching auftreten kann, die möglicherweise verletzt werden #temp_tables, die jedoch durch die Einschränkungen auf @table_variablesjeden Fall verhindert werden. Der Wartungsaufwand für zwischengespeicherte #tempTabellen ist geringfügig höher als für Tabellenvariablen, wie hier dargestellt .

Objekt-Metadaten

Dies ist im Wesentlichen für beide Objekttypen gleich. Es ist in den System-Basistabellen in gespeichert tempdb. Es ist einfacher, eine #tempTabelle zu suchen, da OBJECT_ID('tempdb..#T')sie zum Eingeben der Systemtabellen verwendet werden kann und der intern generierte Name enger mit dem in der CREATE TABLEAnweisung definierten Namen korreliert . Bei Tabellenvariablen object_idfunktioniert die Funktion nicht und der interne Name wird vollständig vom System generiert, ohne dass eine Beziehung zum Variablennamen besteht. Das Folgende zeigt, dass die Metadaten immer noch vorhanden sind, indem Sie einen (hoffentlich eindeutigen) Spaltennamen eingeben. Für Tabellen ohne eindeutigen Spaltennamen kann die object_id mit bestimmt werden DBCC PAGE, solange sie nicht leer sind.

/*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'

Ausgabe

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                  |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+

Transaktionen

Vorgänge für @table_variableswerden als Systemtransaktionen ausgeführt, unabhängig von äußeren Benutzertransaktionen, wohingegen die entsprechenden #tempTabellenvorgänge als Teil der Benutzertransaktion selbst ausgeführt würden. Aus diesem Grund wirkt sich ein ROLLBACKBefehl auf eine #tempTabelle aus, lässt die Tabelle jedoch @table_variableunberührt.

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

Protokollierung

Beide generieren Protokollsätze zum tempdbTransaktionsprotokoll. Ein häufiges Missverständnis ist, dass dies bei Tabellenvariablen nicht der Fall ist. Ein Skript, das dies demonstriert, deklariert eine Tabellenvariable, fügt ein paar Zeilen hinzu, aktualisiert sie und löscht sie.

Da die Tabellenvariable zu Beginn und am Ende des Stapels implizit erstellt und gelöscht wird, müssen mehrere Stapel verwendet werden, um die vollständige Protokollierung anzuzeigen.

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 

Kehrt zurück

Detaillierte Ansicht

Screenshot der Ergebnisse

Zusammenfassungsansicht (einschließlich Protokollierung für implizite Drop- und Systembasistabellen)

Screenshot der Ergebnisse

Soweit ich in der Lage war, Operationen an beiden zu unterscheiden, werden ungefähr die gleichen Mengen an Protokollierung generiert.

Während die Menge der Protokollierung ist sehr ähnlich ein wichtiger Unterschied ist im Zusammenhang , dass die Protokollaufzeichnungen #tempTabellen nicht geräumt werden können , bis jede enthält Transaktion Benutzer beendet so eine lange Transaktion ausgeführt wird, dass zu einem bestimmten Zeitpunkt schreibt #tempTabellen Logkürzung in verhindern , tempdbwährend der autonomen Transaktionen für Tabellenvariablen erzeugt nicht.

Tabellenvariablen werden nicht unterstützt, TRUNCATEdaher kann dies zu einem Protokollierungsnachteil führen, wenn alle Zeilen aus einer Tabelle entfernt werden müssen (obwohl dies bei sehr kleinen Tabellen DELETE ohnehin besser funktionieren kann ).

Kardinalität

Viele der Ausführungspläne mit Tabellenvariablen enthalten eine einzelne Zeile, die als Ausgabe von ihnen geschätzt wird. Die Untersuchung der Eigenschaften der Tabellenvariablen zeigt, dass SQL Server der Ansicht ist, dass die Tabellenvariable Null Zeilen enthält (Warum schätzt man, dass aus einer Null-Zeilen-Tabelle 1 Zeile ausgegeben wird, wird hier von @Paul White erläutert ).

Die im vorherigen Abschnitt gezeigten Ergebnisse zeigen jedoch eine genaue rowsZählung in sys.partitions. Das Problem ist, dass in den meisten Fällen die Anweisungen, die auf Tabellenvariablen verweisen, kompiliert werden, während die Tabelle leer ist. Wenn die Anweisung (neu) kompiliert wird, nachdem sie @table_variableausgefüllt wurde, wird dies stattdessen für die Kardinalität der Tabelle verwendet 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

Der Plan zeigt die genaue geschätzte Zeilenanzahl nach der verzögerten Kompilierung.

Zeigt die genaue Zeilenzahl an

In SQL Server 2012 SP2 wird das Ablaufverfolgungsflag 2453 eingeführt. Weitere Details finden Sie unter "Relational Engine" hier .

Wenn dieses Ablaufverfolgungsflag aktiviert ist, kann es dazu führen, dass die automatische Neukompilierung die geänderte Kardinalität berücksichtigt, wie in Kürze erläutert wird.

NB: Auf Azure in Kompatibilitätsstufe 150 wird die Kompilierung der Anweisung jetzt bis zur ersten Ausführung zurückgestellt . Dies bedeutet, dass das Problem der Nullzeilenschätzung nicht mehr auftritt.

Keine Spaltenstatistik

Eine genauere Tabellenkardinalität bedeutet jedoch nicht, dass die geschätzte Zeilenzahl genauer ist (es sei denn, Sie führen eine Operation für alle Zeilen in der Tabelle aus). SQL Server verwaltet überhaupt keine Spaltenstatistiken für Tabellenvariablen und greift daher auf Schätzungen zurück, die auf dem Vergleichsprädikat basieren (z. B. dass 10% der Tabelle für =eine nicht eindeutige Spalte oder 30% für einen >Vergleich zurückgegeben werden). Im Gegensatz Spaltenstatistiken werden für beibehalten #tempTabellen.

SQL Server zählt die Anzahl der an jeder Spalte vorgenommenen Änderungen. Wenn die Anzahl der Änderungen seit dem Kompilieren des Plans den Neukompilierungsschwellenwert (RT) überschreitet, wird der Plan neu kompiliert und die Statistik aktualisiert. Die RT hängt vom Tabellentyp und der Größe ab.

Aus Plan Caching in SQL Server 2008

RT wird wie folgt berechnet. (n bezieht sich auf die Kardinalität einer Tabelle, wenn ein Abfrageplan kompiliert wird.)

Permanente Tabelle
- Wenn n <= 500, RT = 500.
- Wenn n> 500, RT = 500 + 0,20 * n.

Temporäre Tabelle
- Wenn n <6, RT = 6.
- Wenn 6 <= n <= 500, RT = 500.
- Wenn n> 500, RT = 500 + 0,20 * n.
Tabellenvariable
- RT existiert nicht. Daher finden Neukompilierungen aufgrund von Änderungen der Kardinalitäten von Tabellenvariablen nicht statt. (Siehe Hinweis zu TF 2453 weiter unten)

Der KEEP PLANHinweis kann verwendet werden, um die RT für #tempTabellen wie für permanente Tabellen festzulegen.

Der Nettoeffekt davon ist, dass die für #tempTabellen generierten Ausführungspläne häufig um Größenordnungen besser sind, als @table_variableswenn viele Zeilen beteiligt sind, da SQL Server bessere Informationen zum Arbeiten hat.

NB1: Tabellenvariablen haben keine Statistik, können jedoch ein Neukompilierungsereignis "Statistik geändert" unter dem Ablaufverfolgungsflag 2453 verursachen (gilt nicht für "triviale" Pläne). Dies scheint unter denselben Neukompilierungsschwellenwerten zu erfolgen, wie oben für temporäre Tabellen mit einem angegeben zusätzliche, wenn N=0 -> RT = 1. dh alle Anweisungen, die kompiliert werden, wenn die Tabellenvariable leer ist, erhalten eine Neukompilierung und werden korrigiert, TableCardinalitywenn sie das erste Mal ausgeführt werden, wenn sie nicht leer sind. Die Kardinalität des Kompilierungszeitplans wird im Plan gespeichert. Wenn die Anweisung erneut mit derselben Kardinalität ausgeführt wird (entweder aufgrund von Steueranweisungen oder der Wiederverwendung eines zwischengespeicherten Plans), erfolgt keine Neukompilierung.

Anmerkung 2: Für zwischengespeicherte temporäre Tabellen in gespeicherten Prozeduren ist die Rekompilierungsgeschichte viel komplizierter als oben beschrieben. Alle wichtigen Details finden Sie unter Temporäre Tabellen in gespeicherten Prozeduren .

Kompiliert neu

Sowie die Modifikation basiert Neukompilierungen oben beschriebenen #tempTabellen können auch mit in Verbindung gebracht werden zusätzliche compiliert , nur weil sie Operationen ermöglichen , die für Tabellenvariablen verboten sind , die eine Kompilierung (zB DDL Änderungen auslösen CREATE INDEX, ALTER TABLE)

Verriegelung

Es wurde angegeben, dass Tabellenvariablen nicht am Sperren teilnehmen. Das ist nicht der Fall. Durch Ausführen der folgenden Ausgaben auf der Registerkarte SSMS-Nachrichten werden die Details der Sperren angezeigt, die für eine insert-Anweisung verwendet und freigegeben wurden.

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)

Bei Abfragen, die SELECTvon Tabellenvariablen stammen, weist Paul White in den Kommentaren darauf hin, dass diese automatisch mit einem impliziten NOLOCKHinweis versehen sind. Dies ist unten dargestellt

DECLARE @T TABLE(X INT); 

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

Ausgabe

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

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

Die Auswirkung auf das Sperren ist jedoch möglicherweise recht gering.

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

Keine dieser Rückgaben führt zu einer Indexschlüsselreihenfolge, die angibt, dass SQL Server für beide einen nach Zuordnung sortierten Scan verwendet hat .

Ich habe das obige Skript zweimal ausgeführt und die Ergebnisse für den zweiten Durchlauf sind unten

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 

Die Sperrausgabe für die Tabellenvariable ist in der Tat äußerst gering, da SQL Server nur eine Schemastabilitätssperre für das Objekt erhält. Aber für einen #tempTisch ist es fast so leicht, dass er eine SSperre auf Objektebene aufhebt. Ein NOLOCKHinweis oder eine READ UNCOMMITTEDIsolationsstufe kann natürlich auch beim Arbeiten mit #tempTabellen explizit angegeben werden .

Ähnlich wie bei der Protokollierung kann eine umgebende Benutzertransaktion dazu führen, dass die Sperren für #tempTabellen länger gehalten werden. Mit dem Skript unten

    --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  

Wenn Sie in beiden Fällen außerhalb einer expliziten Benutzertransaktion ausgeführt werden, sys.dm_tran_lockswird bei der Überprüfung nur eine gemeinsame Sperre für das zurückgegeben DATABASE.

Beim Aufheben des Kommentars werden die BEGIN TRAN ... ROLLBACK26 Zeilen zurückgegeben. Dies zeigt, dass Sperren sowohl für das Objekt selbst als auch für Systemtabellenzeilen bestehen, um ein Rollback zu ermöglichen und zu verhindern, dass andere Transaktionen nicht festgeschriebene Daten lesen. Die entsprechende Tabellenvariablenoperation unterliegt bei der Benutzertransaktion keinem Rollback und muss diese Sperren nicht halten, damit wir die nächste Anweisung einchecken können. Die Ablaufverfolgung von Sperren, die im Profiler erworben und freigegeben wurden oder die das Ablaufverfolgungsflag 1200 verwenden, zeigt jedoch, dass noch viele Sperrereignisse vorhanden sind auftreten.

Indizes

In Versionen vor SQL Server 2014 können Indizes nur implizit für Tabellenvariablen als Nebeneffekt des Hinzufügens einer eindeutigen Einschränkung oder eines eindeutigen Primärschlüssels erstellt werden. Dies bedeutet natürlich, dass nur eindeutige Indizes unterstützt werden. Ein nicht eindeutiger nicht gruppierter Index für eine Tabelle mit einem eindeutigen gruppierten Index kann jedoch simuliert werden, indem er einfach deklariert UNIQUE NONCLUSTEREDund der CI-Schlüssel am Ende des gewünschten NCI-Schlüssels hinzugefügt wird (SQL Server würde dies ohnehin hinter den Kulissen tun, selbst wenn ein nicht eindeutiger Index vorliegt NCI kann angegeben werden)

Wie bereits gezeigt, können verschiedene index_options in der Constraint-Deklaration angegeben werden, einschließlich DATA_COMPRESSION, IGNORE_DUP_KEYund FILLFACTOR(obwohl es keinen Sinn macht, diese zu setzen, da dies nur einen Unterschied bei der Index-Neuerstellung macht und Sie keine Indizes für Tabellenvariablen neu erstellen können!)

Darüber hinaus unterstützen Tabellenvariablen keine INCLUDEd-Spalten, gefilterten Indizes (bis 2016) oder Partitionierung, #tempTabellen jedoch (das Partitionsschema muss erstellt werden tempdb).

Indizes in SQL Server 2014

Nicht eindeutige Indizes können in der Definition der Tabellenvariablen in SQL Server 2014 als Inline deklariert werden. Die Beispielsyntax hierfür ist unten aufgeführt.

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*/
);

Indizes in SQL Server 2016

Ab CTP 3.1 ist es jetzt möglich, gefilterte Indizes für Tabellenvariablen zu deklarieren. Bei RTM ist es möglich , dass auch eingeschlossene Spalten zulässig sind, obwohl sie es aufgrund von Ressourcenbeschränkungen wahrscheinlich nicht in SQL16 schaffen

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

Parallelität

Abfragen, die in @table_variableseinen parallelen Plan eingefügt (oder auf andere Weise geändert) werden können, #temp_tablessind auf diese Weise nicht eingeschränkt.

Es gibt eine offensichtliche Problemumgehung, die das Umschreiben wie folgt ermöglicht: Der SELECTTeil kann parallel ausgeführt werden, es wird jedoch eine verborgene temporäre Tabelle verwendet (hinter den Kulissen).

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

Es gibt keine solche Einschränkung bei Abfragen, die aus Tabellenvariablen auswählen , wie in meiner Antwort hier dargestellt

Andere funktionale Unterschiede

  • #temp_tablesKann nicht in einer Funktion verwendet werden. @table_variablesKann in skalaren oder Tabellen-UDFs mit mehreren Anweisungen verwendet werden.
  • @table_variables kann keine benannten Bedingungen haben.
  • @table_variableskann nicht SELECT-ed INTO, ALTER-ed, TRUNCATEd oder das Ziel von DBCCBefehlen wie DBCC CHECKIDENToder von sein SET IDENTITY INSERTund unterstützt keine Tabellenhinweise wieWITH (FORCESCAN)
  • CHECK Einschränkungen für Tabellenvariablen werden vom Optimierer zur Vereinfachung, für implizite Prädikate oder zur Erkennung von Widersprüchen nicht berücksichtigt.
  • Tabellenvariablen sind anscheinend nicht für die Optimierung der gemeinsamen Nutzung von Rowsets geeignet, dh das Löschen und Aktualisieren von Plänen für diese Variablen kann zu einem höheren Overhead und PAGELATCH_EXWartezeiten führen. ( Beispiel )

Nur Speicher?

Wie eingangs erwähnt, werden beide auf Seiten in gespeichert tempdb. Ich habe jedoch nicht angesprochen, ob es einen Unterschied im Verhalten beim Schreiben dieser Seiten auf Disc gibt.

Ich habe dies jetzt ein wenig getestet und bisher keinen solchen Unterschied festgestellt. In dem spezifischen Test, den ich auf meiner Instanz von SQL Server 250 durchgeführt habe, scheint es, dass Seiten der Cut-Off-Punkt sind, bevor die Datendatei geschrieben wird.

Hinweis: Das folgende Verhalten tritt in SQL Server 2014 oder SQL Server 2012 SP1 / CU10 oder SP2 / CU1 nicht mehr auf. Weitere Details zu dieser Änderung in SQL Server 2014: tempdb Hidden Performance Gem .

Führen Sie das folgende Skript aus

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

Und Überwachung schreibt in die tempdbDatendatei mit Prozessmonitor Ich sah keine (außer gelegentlich auf der Datenbank-Boot-Seite bei Versatz 73.728). Nachdem ich zu gewechselt hatte 250, 251begann ich, die folgenden Schriften zu sehen.

ProcMon

Der obige Screenshot zeigt 5 * 32 Seitenschreibvorgänge und einen einzelnen Seitenschreibvorgang, der angibt, dass 161 der Seiten auf eine Disc geschrieben wurden. Ich habe den gleichen Grenzwert von 250 Seiten beim Testen mit Tabellenvariablen erhalten. Das folgende Skript zeigt es auf eine andere Art und Weisesys.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 

Ergebnisse

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

Es wurde angezeigt, dass 192 Seiten auf die Disc geschrieben und die unsaubere Flagge gelöscht wurde. Es zeigt auch, dass das Beschreiben von Datenträgern nicht bedeutet, dass Seiten sofort aus dem Pufferpool entfernt werden. Die Abfragen für diese Tabellenvariable konnten immer noch vollständig aus dem Speicher ausgeführt werden.

Auf einem inaktiven Server max server memory, auf dem Pufferpoolseiten festgelegt 2000 MBund DBCC MEMORYSTATUSgemeldet sind, die als ca. 1.843.000 KB (ca. 23.000 Seiten) zugewiesen sind, habe ich in Stapeln von 1.000 Zeilen / Seiten in die obigen Tabellen eingefügt und für jede Iteration aufgezeichnet.

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

Sowohl die Tabellenvariable als auch die #tempTabelle gaben nahezu identische Diagramme wieder und konnten den Pufferpool so gut wie ausschöpfen, bevor sie zu dem Punkt gelangten, dass sie nicht vollständig im Arbeitsspeicher gespeichert waren, sodass es keine besondere Einschränkung hinsichtlich des Arbeitsspeichers zu geben scheint beides kann verbrauchen.

Seiten im Pufferpool

Martin Smith
quelle
Ich habe festgestellt, dass SQL Server beim Erstellen von temporären Tabellen (auch mit Caching) erheblich mehr Latches erhält als eine entsprechende Tabellenvariable. Sie können testen, indem Sie das latch_acquired-Debug-XE verwenden und eine Tabelle mit etwa 35 Spalten erstellen. Ich fand die Tabellenvariable für 4 Latches und die temporäre Tabelle für etwa 70 Latches.
Joe Obbish
40

Es gibt ein paar Dinge, auf die ich eher aufgrund bestimmter Erfahrungen hinweisen möchte, als dass ich sie studiere. Als DBA bin ich sehr neu. Bitte korrigieren Sie mich, wo erforderlich.

  1. # temporäre Tabellen verwenden standardmäßig die Standardkollatierung der SQL Server-Instanz. Sofern nicht anders angegeben, können Probleme beim Vergleichen oder Aktualisieren von Werten zwischen # temporären Tabellen und Datenbanktabellen auftreten, wenn die Masterdb eine andere Sortierung als die Datenbank aufweist. Siehe: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. Ganz auf der Grundlage persönlicher Erfahrungen scheint sich der verfügbare Speicher positiv auf die Leistung auszuwirken. MSDN empfiehlt die Verwendung von Tabellenvariablen zum Speichern kleinerer Ergebnismengen, aber meistens ist der Unterschied nicht einmal spürbar. In größeren Mengen wird jedoch in einigen Fällen deutlich, dass Tabellenvariablen viel speicherintensiver sind und die Abfrage bis zum Durchforsten verlangsamen können.
Kahn
quelle
6
Beachten Sie auch, dass die Kollatierung in # temporären Tabellen die Kollatierung der aufrufenden Datenbank erben kann , wenn Sie SQL Server 2012 verwenden und die Datenbank enthalten ist.
Aaron Bertrand
Klarstellung auf # 2 für kleine und große Sets stackoverflow.com/a/14465163/5224021
GibralterTop