Wydaje się, że jest to obszar z kilkoma mitami i sprzecznymi poglądami.
Jaka jest różnica między zmienną tabeli a lokalną tabelą tymczasową w programie SQL Server?
Wydaje się, że jest to obszar z kilkoma mitami i sprzecznymi poglądami.
Jaka jest różnica między zmienną tabeli a lokalną tabelą tymczasową w programie SQL Server?
Odpowiedzi:
Zawartość
Zastrzeżenie
Ta odpowiedź omawia „klasyczne” zmienne tabeli wprowadzone w SQL Server 2000. SQL Server 2014 w pamięci OLTP wprowadza typy tabel zoptymalizowane pod kątem pamięci. Wystąpienia zmiennych tabelowych są pod wieloma względami inne niż te omówione poniżej! ( więcej szczegółów ).
Miejsce przechowywania
Bez różnicy. Oba są przechowywane w tempdb
.
Widziałem, jak sugeruje to, że w przypadku zmiennych tabeli nie zawsze tak jest, ale można to zweryfikować poniżej
DECLARE @T TABLE(X INT)
INSERT INTO @T VALUES(1),(2)
SELECT sys.fn_PhysLocFormatter(%%physloc%%) AS [File:Page:Slot]
FROM @T
Przykładowe wyniki (zapisano lokalizację w tempdb
2 wierszach)
File:Page:Slot
----------------
(1:148:0)
(1:148:1)
Lokalizacja logiczna
@table_variables
zachowują się bardziej, jakby były częścią bieżącej bazy danych niż #temp
tabele. W przypadku zmiennych tabeli (od 2005 r.) Zestawienia kolumn, jeśli nie zostaną wyraźnie określone, będą dotyczyły bieżącej bazy danych, natomiast w przypadku #temp
tabel użyje domyślnego zestawienia tempdb
( Więcej szczegółów ). Ponadto typy danych i kolekcje XML zdefiniowane przez użytkownika muszą mieć tempdb, aby mogły być używane w #temp
tabelach, ale zmienne tabel mogą je wykorzystywać z bieżącej bazy danych ( Źródło ).
SQL Server 2012 wprowadza zawarte bazy danych. zachowanie tabel tymczasowych w tych jest różne (h / t Aaron)
W zawartej bazie danych dane tabeli tymczasowej są zestawiane w zestawieniu zawartej bazy danych.
- Wszystkie metadane powiązane z tabelami tymczasowymi (na przykład nazwy tabel i kolumn, indeksy itd.) Znajdą się w sortowaniu katalogu.
- Ograniczenia nazwane nie mogą być używane w tabelach tymczasowych.
- Tabele tymczasowe mogą nie odwoływać się do typów zdefiniowanych przez użytkownika, kolekcji schematów XML lub funkcji zdefiniowanych przez użytkownika.
Widoczność w różnych zakresach
@table_variables
są dostępne tylko w ramach partii i zakresu, w którym zostały zadeklarowane. #temp_tables
są dostępne w ramach partii potomnych (wyzwalacze zagnieżdżone, procedura, exec
wywołania). #temp_tables
utworzone w zewnętrznym zakresie ( @@NESTLEVEL=0
) mogą również obejmować partie, ponieważ są one utrzymywane do końca sesji. Żaden typ obiektu nie może zostać utworzony w pakiecie potomnym i można uzyskać do niego dostęp w zakresie wywoływania, jak jednak omówiono poniżej ( można jednak podać ##temp
tabele globalne ).
Dożywotni
@table_variables
są tworzone niejawnie, gdy partia zawierająca DECLARE @.. TABLE
instrukcję jest wykonywana (przed uruchomieniem dowolnego kodu użytkownika w tej partii) i są upuszczane niejawnie na końcu.
Chociaż analizator składni nie pozwoli ci spróbować użyć zmiennej tabelowej przed DECLARE
instrukcją, niejawne tworzenie można zobaczyć poniżej.
IF (1 = 0)
BEGIN
DECLARE @T TABLE(X INT)
END
--Works fine
SELECT *
FROM @T
#temp_tables
są tworzone jawnie po CREATE TABLE
napotkaniu instrukcji TSQL i mogą być upuszczone jawnie z DROP TABLE
lub zostaną upuszczone niejawnie, gdy partia się zakończy (jeśli jest tworzona w partii potomnej z @@NESTLEVEL > 0
) lub gdy sesja zakończy się inaczej.
Uwaga: W ramach przechowywanych procedur oba typy obiektów mogą być buforowane zamiast wielokrotnie tworzyć i upuszczać nowe tabele. Istnieją ograniczenia dotyczące tego, kiedy może wystąpić takie buforowanie, które można jednak naruszać, #temp_tables
ale których ograniczenia i @table_variables
tak zapobiegają. Narzut związany z konserwacją #temp
tabel buforowanych jest nieco większy niż zmiennych tabeli, jak pokazano tutaj .
Metadane obiektu
Jest to w zasadzie to samo dla obu typów obiektów. Jest on przechowywany w systemowych tabelach podstawowych w tempdb
. Łatwiej jest jednak znaleźć #temp
tabelę, ponieważ OBJECT_ID('tempdb..#T')
można jej użyć do wprowadzenia do tabel systemowych, a nazwa wygenerowana wewnętrznie jest ściślej skorelowana z nazwą zdefiniowaną w CREATE TABLE
instrukcji. W przypadku zmiennych tabeli object_id
funkcja nie działa, a nazwa wewnętrzna jest generowana całkowicie przez system, bez związku z nazwą zmiennej. Poniżej pokazano, że metadane nadal tam są, wprowadzając (miejmy nadzieję unikalną) nazwę kolumny. W przypadku tabel bez unikalnych nazw kolumn identyfikator_obiektu można określić przy użyciu, DBCC PAGE
o ile nie są one puste.
/*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'
Wynik
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 |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
Transakcje
Operacje na @table_variables
są przeprowadzane jako transakcje systemowe, niezależnie od jakiejkolwiek zewnętrznej transakcji użytkownika, podczas gdy równoważne #temp
operacje na tablicy byłyby wykonywane jako część samej transakcji użytkownika. Z tego powodu ROLLBACK
polecenie wpłynie na #temp
tabelę, ale pozostawi @table_variable
nietknięte.
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
Wycięcie lasu
Oba generują rekordy dziennika do tempdb
dziennika transakcji. Powszechnym nieporozumieniem jest to, że nie dzieje się tak w przypadku zmiennych tabeli, więc skrypt demonstrujący to znajduje się poniżej, deklaruje zmienną tabeli, dodaje kilka wierszy, a następnie aktualizuje je i usuwa.
Ponieważ zmienna tabeli jest tworzona i upuszczana niejawnie na początku i na końcu partii, konieczne jest użycie wielu partii, aby zobaczyć pełne rejestrowanie.
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
Zwroty
O ile udało mi się dostrzec operacje na obu, generują mniej więcej równe ilości rejestrowania.
Podczas gdy ilość rejestrowania jest bardzo podobna, jedną ważną różnicą jest to, że rekordy dziennika związane z #temp
tabelami nie mogą być usunięte, dopóki żadna zawierająca transakcja użytkownika nie zakończy się tak długo działająca transakcja, że w pewnym momencie zapisywanie do #temp
tabel zapobiegnie obcinaniu dziennika, tempdb
podczas gdy transakcje autonomiczne spawn dla zmiennych tabeli nie.
Zmienne tabel nie obsługują, TRUNCATE
więc może to być niekorzystne z punktu widzenia rejestrowania, gdy wymagane jest usunięcie wszystkich wierszy z tabeli (choć w przypadku bardzo małych tabel i tak DELETE
można to lepiej wykonać )
Kardynalność
Wiele planów wykonania obejmujących zmienne tabeli pokaże pojedynczy wiersz oszacowany jako wynik z nich. Sprawdzanie właściwości zmiennej tabeli pokazuje, że SQL Server uważa, że zmienna tabeli ma zero wierszy (dlaczego szacuje, że 1 wiersz zostanie wyemitowany z tabeli zerowego rzędu wyjaśniono tutaj @Paul White ).
Jednak wyniki przedstawione w poprzedniej sekcji pokazują dokładne rows
zliczanie w sys.partitions
. Problem polega na tym, że w większości przypadków instrukcje odnoszące się do zmiennych tabeli są kompilowane, gdy tabela jest pusta. Jeśli instrukcja zostanie (ponownie) skompilowana po @table_variable
zapełnieniu, zostanie ona użyta zamiast tego dla liczności tabeli (Może się tak zdarzyć z powodu jawnego recompile
lub być może dlatego, że instrukcja odwołuje się również do innego obiektu, który powoduje odroczoną kompilację lub rekompilację).
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 pokazuje dokładną szacunkową liczbę wierszy po odroczeniu kompilacji.
W SQL Server 2012 SP2 wprowadzono flagę śledzenia 2453. Więcej szczegółów pod „Relational Engine” tutaj .
Gdy ta flaga śledzenia jest włączona, może powodować, że automatyczne rekompilacje uwzględniają zmienioną liczność, jak omówiono to bardzo krótko.
Uwaga: Na platformie Azure pod względem zgodności poziom 150 kompilacja instrukcji jest teraz odraczana do pierwszego wykonania . Oznacza to, że nie będzie już podlegał problemowi z oszacowaniem zerowego rzędu.
Brak statystyk kolumn
Dokładniejsza liczność tabeli nie oznacza jednak, że szacowana liczba wierszy będzie bardziej dokładna (chyba że wykona się operację na wszystkich wierszach w tabeli). SQL Server w ogóle nie utrzymuje statystyk kolumn dla zmiennych tabeli, więc powróci do domysłów opartych na predykacie porównania (np. Że 10% tabeli zostanie zwrócone w =
stosunku do niejednoznacznej kolumny lub 30% dla >
porównania). W przeciwieństwie do statystyk w tabelach są utrzymywane #temp
.
SQL Server utrzymuje liczbę modyfikacji wprowadzonych w każdej kolumnie. Jeśli liczba modyfikacji od czasu kompilacji planu przekroczy próg ponownej kompilacji (RT), plan zostanie ponownie skompilowany, a statystyki zaktualizowane. RT zależy od rodzaju i wielkości stołu.
Od planowania buforowania w SQL Server 2008
RT oblicza się w następujący sposób. (n oznacza liczność tabeli podczas kompilacji planu zapytań).
Stała tabela
- Jeśli n <= 500, RT = 500.
- Jeśli n> 500, RT = 500 + 0,20 * n.Tabela tymczasowa
- Jeśli n <6, RT = 6.
- Jeśli 6 <= n <= 500, RT = 500.
- Jeśli n> 500, RT = 500 + 0,20 * n.
Zmienna tabeli
- RT nie istnieje. Dlatego rekompilacje nie zachodzą z powodu zmian liczności zmiennych tabeli. (Ale patrz uwaga na temat TF 2453 poniżej)
KEEP PLAN
wskazówką może być używany do ustawiania RT na #temp
stołach takie same jak dla stałych tablicach.
Efektem tego wszystkiego jest to, że często plany wykonania generowane dla #temp
tabel są o rząd wielkości większe niż w przypadku, @table_variables
gdy zaangażowanych jest wiele wierszy, ponieważ SQL Server ma lepsze informacje do pracy.
NB1: Zmienne tabeli nie mają statystyk, ale mogą nadal powodować zdarzenie ponownej kompilacji „Zmienione statystyki” pod flagą śledzenia 2453 (nie dotyczy planów „trywialnych”). Wydaje się, że występuje to przy tych samych progach ponownej kompilacji, jak pokazano w tabelach temp powyżej dodatkowy, że jeśli N=0 -> RT = 1
. tzn. wszystkie instrukcje skompilowane, gdy zmienna tabeli jest pusta, w końcu otrzymają rekompilację i poprawione TableCardinality
przy pierwszym uruchomieniu, gdy nie będą puste. Liczność tabeli czasowej kompilacji jest zapisywana w planie, a jeśli instrukcja zostanie wykonana ponownie z tą samą licznością (z powodu przepływu instrukcji sterujących lub ponownego użycia planu w pamięci podręcznej), nie nastąpi ponowna kompilacja.
NB2: W przypadku buforowanych tabel tymczasowych w procedurach przechowywanych historia ponownej kompilacji jest znacznie bardziej skomplikowana niż opisano powyżej. Zobacz tabele tymczasowe w procedurach przechowywanych, aby poznać wszystkie szczegóły.
Ponowna kompilacja
A także rekompiluje modyfikacji na podstawie opisanych powyżej #temp
stołów mogą być również związane z dodatkowymi kompilacji po prostu dlatego, że umożliwiają one operacje, które są zabronione dla zmiennych, które powodują tabeli kompilacji (np zmiany DLL CREATE INDEX
, ALTER TABLE
)
Zamykający
Stwierdzono , że zmienne tabeli nie uczestniczą w blokowaniu. Nie o to chodzi. Uruchomienie poniższych danych wyjściowych w zakładce komunikatów SSMS szczegółowe informacje o blokadach przyjętych i zwolnionych dla instrukcji insert.
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)
W przypadku zapytań, które SELECT
ze zmiennych tabeli Paul White wskazuje w komentarzach, że automatycznie pochodzą one z niejawną NOLOCK
wskazówką. Pokazano to poniżej
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 )
Wpływ tego na blokowanie może być jednak niewielki.
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
Żaden z tych zwracanych wyników nie powoduje uporządkowania klucza indeksu, co wskazuje, że SQL Server użył skanowania z przydzielonym przydziałem dla obu.
Uruchomiłem powyższy skrypt dwa razy, a wyniki drugiego uruchomienia są poniżej
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
Dane wyjściowe blokowania dla zmiennej tabeli są rzeczywiście bardzo minimalne, ponieważ SQL Server po prostu uzyskuje blokadę stabilności schematu na obiekcie. Ale dla #temp
stołu jest prawie tak lekki, że usuwa S
blokadę poziomu obiektu . NOLOCK
Wskazówkę lub READ UNCOMMITTED
izolacja poziom może być oczywiście wyraźnie określone podczas pracy z #temp
tabelami, jak również.
Podobnie jak w przypadku rejestrowania transakcji użytkownika w otoczeniu, może to oznaczać, że blokady są przechowywane dłużej dla #temp
tabel. Za pomocą skryptu poniżej
--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
po uruchomieniu w obu przypadkach poza jawną transakcją użytkownika, jedyną blokadą zwróconą podczas sprawdzania sys.dm_tran_locks
jest blokada współdzielona w DATABASE
.
Po odkomentowaniu BEGIN TRAN ... ROLLBACK
zwracane są 26 wierszy pokazujące, że blokady są utrzymywane zarówno na samym obiekcie, jak i na wierszach tabeli systemowej, aby umożliwić wycofanie i uniemożliwić innym transakcjom odczytanie niezaangażowanych danych. Równoważna operacja zmiennej tabeli nie podlega wycofaniu z transakcją użytkownika i nie ma potrzeby trzymania tych blokad, abyśmy mogli sprawdzić w następnej instrukcji, ale śledzenie blokad nabytych i zwolnionych w programie Profiler lub użycie flagi śledzenia 1200 pokazuje, że wiele zdarzeń blokujących wciąż występuje pojawić się.
Indeksy
W wersjach wcześniejszych niż SQL Server 2014 indeksy można tworzyć niejawnie tylko na zmiennych tabeli jako efekt uboczny dodania unikalnego ograniczenia lub klucza podstawowego. To oczywiście oznacza, że obsługiwane są tylko unikalne indeksy. Nie unikalny indeks nieklastrowany w tabeli z unikalnym indeksem klastrowym można jednak zasymulować, po prostu zadeklarując go UNIQUE NONCLUSTERED
i dodając klucz CI na końcu żądanego klucza NCI (SQL Server i tak zrobiłby to za kulisami, nawet jeśli nie jest unikalny Można podać NCI)
Jak wykazano wcześniej, index_option
w deklaracji ograniczeń można określić różne s, w tym DATA_COMPRESSION
, IGNORE_DUP_KEY
i FILLFACTOR
(chociaż nie ma sensu ustawiać tego, ponieważ spowodowałoby to różnicę tylko przy przebudowywaniu indeksu i nie można przebudowywać indeksów na zmiennych tabeli!)
Dodatkowo zmienne tabel nie obsługują INCLUDE
d kolumn, filtrowanych indeksów (do 2016 r.) Ani partycjonowania, #temp
tabele tak (schemat partycji musi zostać utworzony tempdb
).
Indeksy w SQL Server 2014
Niejednorodne indeksy można zadeklarować wbudowane w definicji zmiennej tabeli w SQL Server 2014. Przykładowa składnia tego jest poniżej.
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*/
);
Indeksy w SQL Server 2016
Od CTP 3.1 można teraz deklarować przefiltrowane indeksy dla zmiennych tabeli. Według RTM może się zdarzyć, że dołączone kolumny są również dozwolone, chociaż najprawdopodobniej nie dostaną się do SQL16 z powodu ograniczeń zasobów
DECLARE @T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)
Równoległość
Zapytania wstawiane do (lub w inny sposób modyfikujące) @table_variables
nie mogą mieć planu równoległego, #temp_tables
nie są w ten sposób ograniczone.
Widoczne jest obejście tego przepisywania w następujący sposób, który pozwala na SELECT
równoległą część, ale kończy się to na ukrytym tymczasowym stole (za kulisami)
INSERT INTO @DATA ( ... )
EXEC('SELECT .. FROM ...')
Nie ma takich ograniczeń w zapytaniach, które wybierają spośród zmiennych tabeli, jak pokazano w mojej odpowiedzi tutaj
Inne różnice funkcjonalne
#temp_tables
nie można używać wewnątrz funkcji. @table_variables
może być używany wewnątrz skalarnych lub wielowymiarowych tabel UDF.@table_variables
nie mógł mieć nazwanych ograniczeń.@table_variables
nie może być SELECT
-ed INTO
, ALTER
-ed, TRUNCATE
d ani być celem DBCC
poleceń takich jak DBCC CHECKIDENT
lub lub SET IDENTITY INSERT
i nie obsługuje podpowiedzi tabelowych takich jakWITH (FORCESCAN)
CHECK
ograniczenia dotyczące zmiennych tabeli nie są uwzględniane przez optymalizator w celu uproszczenia, domyślnych predykatów lub wykrywania sprzeczności.PAGELATCH_EX
czeka. ( Przykład )Tylko pamięć?
Jak wspomniano na początku, oba są przechowywane na stronach w tempdb
. Nie zastanawiałem się jednak, czy istnieje jakaś różnica w zachowaniu, jeśli chodzi o pisanie tych stron na dysk.
Przeprowadziłem już niewielką liczbę testów na ten temat i do tej pory nie zauważyłem takiej różnicy. W konkretnym teście, który wykonałem na mojej stronie SQL Server 250 stron wydaje się być punktem odcięcia przed zapisaniem pliku danych.
Uwaga: Poniższe zachowanie nie występuje już w SQL Server 2014 lub SQL Server 2012 SP1 / CU10 lub SP2 / CU1, chętny pisarz nie jest już tak chętny do zapisywania stron na dysku. Więcej szczegółów na temat tej zmiany na SQL Server 2014: tempdb Hidden Performance Gem .
Uruchamianie poniższego skryptu
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
I monitorowanie zapisuje do tempdb
pliku danych za pomocą Monitora procesów, którego nie widziałem (z wyjątkiem tych na stronie startowej bazy danych z przesunięciem 73 728). Po zmianie 250
na 251
zacząłem widzieć napisy jak poniżej.
Powyższy zrzut ekranu pokazuje 5 * 32 zapisy stron i zapis jednej strony, co wskazuje, że 161 stron zapisano na dysku. Otrzymałem ten sam punkt odcięcia wynoszący 250 stron podczas testowania przy użyciu zmiennych tabeli. Poniższy skrypt pokazuje to inaczej, patrząc nasys.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
Wskazuje, że na dysk zapisano 192 strony, a brudna flaga została usunięta. Pokazuje także, że zapis na dysk nie oznacza, że strony zostaną natychmiast usunięte z puli buforów. Zapytania dotyczące tej zmiennej tabeli nadal mogą być całkowicie zaspokojone z pamięci.
Na bezczynnym serwerze z max server memory
ustawionymi 2000 MB
i DBCC MEMORYSTATUS
raportującymi stronami puli buforów Przydzielonymi jako około 1 843 000 KB (ok. 23 000 stron) wstawiłem do powyższych tabel partiami po 1000 wierszy / stron i dla każdej zarejestrowanej iteracji.
SELECT Count(*)
FROM sys.dm_os_buffer_descriptors
WHERE database_id = 2
AND allocation_unit_id = @allocId
AND page_type = 'DATA_PAGE'
Zarówno zmienna tabeli, jak i #temp
tabela dały prawie identyczne wykresy i udało się maksymalnie zwiększyć pulę buforów, zanim doszło do tego, że nie były one całkowicie przechowywane w pamięci, więc nie wydaje się, aby istniało jakieś szczególne ograniczenie ilości pamięci albo może konsumować.
Jest kilka rzeczy, na które chciałbym zwrócić uwagę w oparciu o konkretne doświadczenia, a nie o badania. Jako DBA jestem bardzo nowy, więc proszę popraw mnie tam, gdzie jest to wymagane.