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?
sql-server
t-sql
temporary-tables
Martin Smith
źródło
źródło
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
Przykładowe wyniki (zapisano lokalizację w
tempdb
2 wierszach)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 zestawieniatempdb
( 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)
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ącaDECLARE @.. 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.#temp_tables
są tworzone jawnie poCREATE TABLE
napotkaniu instrukcji TSQL i mogą być upuszczone jawnie zDROP 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ą wCREATE TABLE
instrukcji. W przypadku zmiennych tabeliobject_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.Wynik
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 powoduROLLBACK
polecenie wpłynie na#temp
tabelę, ale pozostawi@table_variable
nietknięte.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.
Zwroty
Widok szczegółowy
Widok podsumowania (obejmuje rejestrowanie niejawnego upuszczenia i systemowe tabele podstawowe)
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 takDELETE
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 wsys.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 jawnegorecompile
lub być może dlatego, że instrukcja odwołuje się również do innego obiektu, który powoduje odroczoną kompilację lub rekompilację).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
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 poprawioneTableCardinality
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 DLLCREATE 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.
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żejWynik
Wpływ tego na blokowanie może być jednak niewielki.
Ż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
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 usuwaS
blokadę poziomu obiektu .NOLOCK
Wskazówkę lubREAD 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żejpo uruchomieniu w obu przypadkach poza jawną transakcją użytkownika, jedyną blokadą zwróconą podczas sprawdzania
sys.dm_tran_locks
jest blokada współdzielona wDATABASE
.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 tymDATA_COMPRESSION
,IGNORE_DUP_KEY
iFILLFACTOR
(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ć utworzonytempdb
).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.
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
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)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
-edINTO
,ALTER
-ed,TRUNCATE
d ani być celemDBCC
poleceń takich jakDBCC CHECKIDENT
lub lubSET 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.
Uruchamianie poniższego skryptu
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 zmianie250
na251
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 na
sys.dm_os_buffer_descriptors
Wyniki
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
ustawionymi2000 MB
iDBCC 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.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ć.źródło
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.
źródło