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?
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?
Antworten:
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 tempdb
den 2 Zeilen werden gespeichert)
File:Page:Slot
----------------
(1:148:0)
(1:148:1)
Logischer Ort
@table_variables
Verhalten Sie sich mehr als ob sie Teil der aktuellen Datenbank wären als #temp
Tabellen. Für Spaltenkollatierungen von Tabellenvariablen (seit 2005) gilt, wenn nicht explizit angegeben, die der aktuellen Datenbank, während für #temp
Tabellen die Standardkollatierung von tempdb
( Weitere Details ) verwendet wird. Außerdem müssen benutzerdefinierte Datentypen und XML-Auflistungen in tempdb vorliegen, damit sie für #temp
Tabellen 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_variables
Der Zugriff ist nur innerhalb des Stapels und Bereichs möglich, in dem sie deklariert sind. #temp_tables
sind in untergeordneten Stapeln zugänglich (verschachtelte Trigger, Prozeduren, exec
Aufrufe). #temp_tables
Am ä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 ##temp
Tabellen verwendet werden).
Lebenszeit
@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 am Ende implizit gelöscht.
Obwohl der Parser nicht zulässt, dass Sie versuchen, die Tabellenvariable vor der DECLARE
Anweisung zu verwenden, wird die implizite Erstellung unten angezeigt.
IF (1 = 0)
BEGIN
DECLARE @T TABLE(X INT)
END
--Works fine
SELECT *
FROM @T
#temp_tables
werden explizit erstellt, wenn die TSQL- CREATE TABLE
Anweisung angetroffen wird, und können explizit mit gelöscht werden DROP TABLE
oder 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_variables
jeden Fall verhindert werden. Der Wartungsaufwand für zwischengespeicherte #temp
Tabellen 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 #temp
Tabelle 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 TABLE
Anweisung definierten Namen korreliert . Bei Tabellenvariablen object_id
funktioniert 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_variables
werden als Systemtransaktionen ausgeführt, unabhängig von äußeren Benutzertransaktionen, wohingegen die entsprechenden #temp
Tabellenvorgänge als Teil der Benutzertransaktion selbst ausgeführt würden. Aus diesem Grund wirkt sich ein ROLLBACK
Befehl auf eine #temp
Tabelle aus, lässt die Tabelle jedoch @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 zum tempdb
Transaktionsprotokoll. 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
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 #temp
Tabellen 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 #temp
Tabellen Logkürzung in verhindern , tempdb
während der autonomen Transaktionen für Tabellenvariablen erzeugt nicht.
Tabellenvariablen werden nicht unterstützt, TRUNCATE
daher 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 rows
Zä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_variable
ausgefü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.
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 #temp
Tabellen.
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 PLAN
Hinweis kann verwendet werden, um die RT für #temp
Tabellen wie für permanente Tabellen festzulegen.
Der Nettoeffekt davon ist, dass die für #temp
Tabellen generierten Ausführungspläne häufig um Größenordnungen besser sind, als @table_variables
wenn 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, TableCardinality
wenn 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 #temp
Tabellen 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 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 dargestellt
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 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 #temp
Tisch ist es fast so leicht, dass er eine S
Sperre auf Objektebene aufhebt. Ein NOLOCK
Hinweis oder eine READ UNCOMMITTED
Isolationsstufe kann natürlich auch beim Arbeiten mit #temp
Tabellen explizit angegeben werden .
Ähnlich wie bei der Protokollierung kann eine umgebende Benutzertransaktion 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 Sie in beiden Fällen außerhalb einer expliziten Benutzertransaktion ausgeführt werden, sys.dm_tran_locks
wird bei der Überprüfung nur eine gemeinsame Sperre für das zurückgegeben DATABASE
.
Beim Aufheben des Kommentars werden die BEGIN TRAN ... ROLLBACK
26 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 NONCLUSTERED
und 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_option
s in der Constraint-Deklaration angegeben werden, einschließlich DATA_COMPRESSION
, IGNORE_DUP_KEY
und 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 INCLUDE
d-Spalten, gefilterten Indizes (bis 2016) oder Partitionierung, #temp
Tabellen 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_variables
einen parallelen Plan eingefügt (oder auf andere Weise geändert) werden können, #temp_tables
sind auf diese Weise nicht eingeschränkt.
Es gibt eine offensichtliche Problemumgehung, die das Umschreiben wie folgt ermöglicht: Der SELECT
Teil 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_tables
Kann nicht in einer Funktion verwendet werden. @table_variables
Kann in skalaren oder Tabellen-UDFs mit mehreren Anweisungen verwendet werden.@table_variables
kann keine benannten Bedingungen haben.@table_variables
kann nicht SELECT
-ed INTO
, ALTER
-ed, TRUNCATE
d oder das Ziel von DBCC
Befehlen wie DBCC CHECKIDENT
oder von sein SET IDENTITY INSERT
und 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.PAGELATCH_EX
Wartezeiten 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 tempdb
Datendatei mit Prozessmonitor Ich sah keine (außer gelegentlich auf der Datenbank-Boot-Seite bei Versatz 73.728). Nachdem ich zu gewechselt hatte 250
, 251
begann ich, die folgenden Schriften zu sehen.
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
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 MB
und DBCC MEMORYSTATUS
gemeldet 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 #temp
Tabelle 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.
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.