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?
Inhalt
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
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 DELETE
besser 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.
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)
*** 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_option
s 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 INCLUDE
d-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, TRUNCATE
d 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.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.
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
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.
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.