it-swarm.com.de

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

Dies scheint ein Gebiet mit einigen Mythen und widersprüchlichen Ansichten zu sein.

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

459
Martin Smith

Inhalt

Contents

Vorsichtsmaßnahme

In dieser Antwort werden "klassische" Tabellenvariablen erläutert, die in SQL Server 2000 eingeführt wurden. SQL Server 2014 im Speicher OLTP führt speicheroptimierte Tabellentypen ein. Die Instanzen dieser Tabellenvariablen unterscheiden sich in vielerlei Hinsicht von den beschriebenen unten! ( mehr Details ).

Speicherort

Kein Unterschied. Beide sind in tempdb gespeichert.

Ich habe gesehen, dass dies für Tabellenvariablen nicht immer der Fall ist, aber dies kann anhand der folgenden Informationen ü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 (Position in tempdb anzeigen Die 2 Zeilen werden gespeichert)

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

Logischer Ort

@table_variables verhält sich eher so, als wären sie Teil der aktuellen Datenbank als #temp Tabellen. Für Tabellenvariablen (seit 2005) werden Spaltenkollatierungen, sofern nicht explizit angegeben, die der aktuellen Datenbank sein, während für #temp -Tabellen die Standardkollatierung von tempdb ( verwendet wird. Weitere Details ). Zusätzlich müssen benutzerdefinierte Datentypen und XML-Sammlungen in Tempdb sein, damit sie für #temp -Tabellen verwendet werden können. Tabellenvariablen können sie jedoch aus der aktuellen Datenbank ( Source ) verwenden.

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

In einer enthaltenen Datenbank werden temporäre Tabellendaten in der Sortierung der enthaltenen Datenbank sortiert.

  • 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 beziehen sich möglicherweise nicht auf benutzerdefinierte Typen, XML-Schemasammlungen oder benutzerdefinierte Funktionen.

Sichtbarkeit zu verschiedenen Bereichen

Auf @table_variables kann nur innerhalb des Stapels und Bereichs zugegriffen werden, in dem sie deklariert sind. Auf #temp_tables kann innerhalb untergeordneter Stapel zugegriffen werden (verschachtelte Trigger, Prozedur, exec Aufrufe). #temp_tables, der im äußeren Bereich erstellt wurde (@@NESTLEVEL=0), kann auch Stapel umfassen, da sie bis zum Ende der Sitzung bestehen bleiben. Keiner der Objekttypen kann jedoch in einem untergeordneten Stapel erstellt und im aufrufenden Bereich aufgerufen werden, wie im Folgenden erläutert (globale ##temp-Tabellen can be).

Lebensdauer

@table_variables werden implizit erstellt, wenn ein Stapel mit einer DECLARE @.. TABLE- Anweisung ausgeführt wird (bevor ein Benutzercode in diesem Stapel ausgeführt wird) und werden am Ende implizit gelöscht.

Obwohl der Parser es Ihnen nicht erlaubt, die Tabellenvariable vor der Anweisung DECLARE zu verwenden, ist die implizite Erstellung unten dargestellt.

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

--Works fine
SELECT *
FROM @T

#temp_tables werden explizit erstellt, wenn die TSQL-Anweisung CREATE TABLE angetroffen wird, und können explizit mit DROP TABLE gelöscht werden oder werden implizit gelöscht, wenn der Stapel endet (wenn er in einem untergeordneten Stapel mit @@NESTLEVEL > 0 erstellt wurde) oder wenn die Sitzung anderweitig endet.

NB: Innerhalb gespeicherter 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 für #temp_tables verletzt werden können, die jedoch durch die Einschränkungen für @table_variables ohnehin verhindert werden. Der Wartungsaufwand für zwischengespeicherte #temp-Tabellen ist geringfügig größer als für Tabellenvariablen , wie hier dargestellt .

Objektmetadaten

Dies ist im Wesentlichen für beide Objekttypen gleich. Es wird in den Systembasistabellen in tempdb gespeichert. Es ist einfacher, nach einer #temp-Tabelle zu suchen, da OBJECT_ID('tempdb..#T') zum Eingeben der Systemtabellen verwendet werden kann und der intern generierte Name enger mit dem in der CREATE TABLE-Anweisung definierten Namen korreliert. Bei Tabellenvariablen funktioniert die Funktion object_id 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 eindeutige Spaltennamen kann die Objekt-ID mit DBCC PAGE ermittelt werden, 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

Operationen an @table_variables werden als Systemtransaktionen ausgeführt, unabhängig von äußeren Benutzertransaktionen, während die entsprechenden Tabellenoperationen #temp als Teil der Benutzertransaktion selbst ausgeführt werden. Aus diesem Grund wirkt sich ein ROLLBACK -Befehl auf eine #temp-Tabelle aus, lässt jedoch den @table_variable unberü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 für das Transaktionsprotokoll tempdb. Ein häufiges Missverständnis ist, dass dies bei Tabellenvariablen nicht der Fall ist. Ein Skript, das dies demonstriert, deklariert eine Tabellenvariable, fügt einige 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 of results

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

Screenshot of results

Soweit ich in der Lage war, Operationen auf beiden zu erkennen, wird ungefähr die gleiche Menge an Protokollierung generiert.

Während die Menge der Protokollierung sehr ähnlich ist, besteht ein wichtiger Unterschied darin, dass Protokolldatensätze, die sich auf #temp-Tabellen beziehen, erst gelöscht werden können, wenn eine enthaltende Benutzertransaktion abgeschlossen ist, sodass eine lange laufende Transaktion ausgeführt wird, in die irgendwann geschrieben wird #temp Tabellen verhindern das Abschneiden von Protokollen in tempdb, während die für Tabellenvariablen erzeugten autonomen Transaktionen dies nicht tun.

Tabellenvariablen unterstützen TRUNCATE nicht und können daher einen Protokollierungsnachteil haben, wenn alle Zeilen aus einer Tabelle entfernt werden müssen (obwohl für sehr kleine Tabellen DELETEbesser funktionieren kann sowieso )

Kardinalität

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

Die im vorherigen Abschnitt gezeigten Ergebnisse zeigen jedoch eine genaue Anzahl von rows 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 nach dem Auffüllen von @table_variable (erneut) kompiliert wird, wird dies stattdessen für die Kardinalität der Tabelle verwendet (Dies kann aufgrund eines expliziten recompile geschehen oder möglicherweise, weil die Anweisung auch auf ein anderes Objekt verweist, das eine verzögerte Kompilierung verursacht oder eine Neukompilierung.)

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

Plan zeigt genaue geschätzte Zeilenanzahl nach verzögerter Kompilierung.

Shows accurate row count

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 dies dazu führen, dass automatische Neukompilierungen die geänderte Kardinalität berücksichtigen, wie in Kürze näher erläutert wird.

Hinweis: Unter Azure in Kompatibilitätsstufe 150 wird die Kompilierung der Anweisung jetzt bis zur ersten Ausführung verschoben. Dies bedeutet, dass das Problem der Nullzeilenschätzung nicht mehr besteht.

Keine Spaltenstatistik

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

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

Von Plan-Caching in SQL Server 2008

RT wird wie folgt berechnet. (n bezieht sich auf die Kardinalität einer Tabelle, wenn ein Abfrageplan erstellt 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 unten)

mit dem Hinweis KEEP PLAN können Sie die RT für #temp-Tabellen wie für permanente Tabellen festlegen.

Der Nettoeffekt all dessen ist, dass die für #temp-Tabellen generierten Ausführungspläne häufig um Größenordnungen besser sind als für @table_variables, wenn viele Zeilen beteiligt sind, da SQL Server über bessere Informationen zum Arbeiten verfügt.

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

NB2: Für zwischengespeicherte temporäre Tabellen in gespeicherten Prozeduren ist die Neukompilierungsgeschichte viel komplizierter als oben beschrieben. Siehe Temporäre Tabellen in gespeicherten Prozeduren für alle wichtigen Details.

Kompiliert neu

Neben den oben beschriebenen modifikationsbasierten Neukompilierungen können #temp -Tabellen auch zusätzlichen Kompilierungen zugeordnet werden, da sie Operationen zulassen, die für Tabellenvariablen, die eine Kompilierung auslösen, verboten sind (z. B. DDL-Änderungen CREATE INDEX, ALTER TABLE )

Sperren

Es wurde angegeben , dass Tabellenvariablen nicht am Sperren beteiligt sind. Das ist nicht der Fall. Wenn Sie die folgenden Ausgaben auf der Registerkarte SSMS-Nachrichten ausführen, werden die Details der Sperren angezeigt, die für eine Einfügeanweisung vorgenommen 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 SELECT von Tabellenvariablen stammen, weist Paul White in den Kommentaren darauf hin, dass diese automatisch mit einem impliziten NOLOCK Hinweis versehen sind. Dies ist unten gezeigt

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 Auswirkungen auf die Verriegelung können jedoch recht gering sein.

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 Zuordnungs-Scan verwendet hat.

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

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 eine #temp-Tabelle ist es fast so leicht, dass sie eine Sperre auf Objektebene S aufhebt. Ein NOLOCK Hinweis oder eine Isolationsstufe READ UNCOMMITTED kann natürlich auch explizit angegeben werden, wenn mit Tabellen #temp gearbeitet wird.

Ähnlich wie beim Protokollieren einer umgebenden Benutzertransaktion kann dies dazu führen, dass die Sperren für #temp-Tabellen 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 in beiden Fällen außerhalb einer expliziten Benutzertransaktion ausgeführt, ist die einzige Sperre, die beim Überprüfen von sys.dm_tran_locks zurückgegeben wird, eine gemeinsam genutzte Sperre für DATABASE.

Beim Kommentieren des BEGIN TRAN ... ROLLBACK werden 26 Zeilen zurückgegeben, die zeigen, dass Sperren sowohl für das Objekt selbst als auch für Systemtabellenzeilen gehalten werden, um ein Rollback zu ermöglichen und zu verhindern, dass andere Transaktionen nicht festgeschriebene Daten lesen. Die äquivalente Tabellenvariablenoperation unterliegt keinem Rollback mit der Benutzertransaktion und muss diese Sperren nicht halten, damit wir die nächste Anweisung einchecken können. Die in Profiler erfasste und freigegebene Ablaufverfolgungssperre oder die Verwendung des Ablaufverfolgungsflags 1200 zeigt jedoch, dass noch viele Sperrereignisse vorhanden sind auftreten.

Indizes

Für Versionen vor SQL Server 2014 können Indizes nur implizit für Tabellenvariablen als Nebeneffekt beim Hinzufügen 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 einfach UNIQUE NONCLUSTERED deklariert und der CI-Schlüssel am Ende des gewünschten NCI-Schlüssels hinzugefügt wird (SQL Server würde dies hinter den Kulissen tun trotzdem auch wenn ein nicht eindeutiger NCI angegeben werden könnte)

Wie bereits gezeigt, können in der Einschränkungsdeklaration verschiedene index_options angegeben werden, einschließlich DATA_COMPRESSION, IGNORE_DUP_KEY und FILLFACTOR (obwohl es keinen Sinn macht, diesen festzulegen, da dies nur bei der Indexwiederherstellung einen Unterschied macht und Sie nicht neu erstellen können Indizes für Tabellenvariablen!)

Zusätzlich unterstützen Tabellenvariablen keine INCLUDEd-Spalten, gefilterten Indizes (bis 2016) oder Partitionierung, #temp Tabellen nicht (das Partitionsschema muss in tempdb erstellt werden).

Indizes in SQL Server 2014

Nicht eindeutige Indizes können in der Tabellenvariablendefinition in SQL Server 2014 inline deklariert werden. Eine Beispielsyntax hierfür finden Sie unten.

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 können jetzt gefilterte Indizes für Tabellenvariablen deklariert werden. Von RTM it may ist der Fall, dass eingeschlossene Spalten ebenfalls zulässig sind, obwohl sie aufgrund von Ressourcenbeschränkungen wahrscheinlich nicht in SQL16 gelangen

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

Parallelität

Abfragen, die in @table_variables eingefügt (oder anderweitig geändert) werden, können keinen parallelen Plan haben. #temp_tables wird auf diese Weise nicht eingeschränkt.

Es gibt eine offensichtliche Problemumgehung, wenn das Umschreiben wie folgt ermöglicht, dass der Teil SELECT parallel ausgeführt wird, dies jedoch dazu führt, dass eine versteckte temporäre Tabelle (hinter den Kulissen) verwendet wird.

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

Es gibt keine solche Einschränkung bei Abfragen, bei denen aus Tabellenvariablen ausgewählt wird, wie in meiner Antwort hier dargestellt .

Andere funktionale Unterschiede

  • #temp_tables kann nicht innerhalb einer Funktion verwendet werden. @table_variables kann in skalaren UDFs oder Tabellen mit mehreren Anweisungen verwendet werden.
  • @table_variables kann keine benannten Einschränkungen haben.
  • @table_variables kann nicht SELECT- ed INTO, ALTER- ed, TRUNCATEd sein oder das Ziel von DBCC Befehlen wie DBCC CHECKIDENT oder von sein SET IDENTITY INSERT und unterstützen keine Tabellenhinweise wie WITH (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 scheinen sich nicht für die Optimierung der Rowset-Freigabe zu qualifizieren, was bedeutet, dass das Löschen und Aktualisieren von Plänen für diese Variablen mehr Overhead verursachen und PAGELATCH_EX warten kann. ( Beispiel )

Nur Speicher?

Wie eingangs erwähnt, werden beide auf Seiten in tempdb gespeichert. Ich habe jedoch nicht angesprochen, ob es beim Schreiben dieser Seiten auf Disc einen Unterschied im Verhalten 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 durchgeführt habe, scheinen 250 Seiten der Grenzwert zu sein, bevor die Datendatei beschrieben wird.

Hinweis: Das folgende Verhalten tritt in SQL Server 2014 oder SQL Server 2012 SP1/CU10 oder SP2/CU1 nicht mehr auf. Der eifrige Schreiber ist nicht mehr so ​​eifrig, Seiten auf Disc zu schreiben. Weitere Details zu dieser Änderung finden Sie unter 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 beim Überwachen von Schreibvorgängen in die tempdb -Datendatei mit Process Monitor habe ich keine gesehen (außer gelegentlich auf der Datenbank-Startseite mit Offset 73.728). Nachdem ich 250 in 251 geändert hatte, sah ich die folgenden Schreibvorgänge.

ProcMon

Der Screenshot oben zeigt 5 * 32-Seiten-Schreibvorgänge und einen einzelnen Seiten-Schreibvorgang, der angibt, dass 161 der Seiten auf Disc geschrieben wurden. Beim Testen mit Tabellenvariablen habe ich den gleichen Grenzwert von 250 Seiten erreicht. Das folgende Skript zeigt es anders, indem es sich sys.dm_os_buffer_descriptors ansieht

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

Dies zeigt, dass 192 Seiten auf die Disc geschrieben und die schmutzige Flagge gelöscht wurden. Es zeigt auch, dass das Schreiben auf eine Disc nicht bedeutet, dass Seiten sofort aus dem Pufferpool entfernt werden. Die Abfragen für diese Tabellenvariable konnten immer noch vollständig aus dem Speicher erfüllt werden.

Auf einem inaktiven Server, auf dem max server memory auf 2000 MB und DBCC MEMORYSTATUS gesetzt ist, werden Pufferpoolseiten mit einer Zuordnung von ca. 1.843.000 KB (ca. 23.000 Seiten) in Stapeln von 1.000 Zeilen/Seiten und für jede aufgezeichnete Iteration in die obigen Tabellen eingefügt.

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 Tabelle #temp ergaben nahezu identische Diagramme und schafften es, den Pufferpool so gut wie zu maximieren, bevor sie zu dem Punkt kamen, dass sie nicht vollständig im Speicher gespeichert waren, sodass es keine besondere Einschränkung hinsichtlich der Höhe zu geben scheint Speicher kann entweder verbrauchen.

Pages in Buffer Pool

682
Martin Smith

Es gibt einige Dinge, auf die ich eher aufgrund bestimmter Erfahrungen als aufgrund des Studiums hinweisen möchte. Als DBA bin ich sehr neu. Bitte korrigieren Sie mich bei Bedarf.

  1. # temp-Tabellen verwenden standardmäßig die Standardkollatierung der SQL Server-Instanz. Sofern nicht anders angegeben, können Probleme beim Vergleichen oder Aktualisieren von Werten zwischen # temp-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. Vollständig aufgrund persönlicher Erfahrungen scheint das verfügbare Gedächtnis einen Effekt zu haben, der eine bessere Leistung erbringt. MSDN empfiehlt die Verwendung von Tabellenvariablen zum Speichern kleinerer Ergebnismengen, aber meistens ist der Unterschied nicht einmal spürbar. In größeren Gruppen wird jedoch in einigen Fällen deutlich, dass Tabellenvariablen viel speicherintensiver sind und die Abfrage bis zum Crawlen verlangsamen können.
41
Kahn