Jaka jest różnica między tabelą tymczasową a zmienną tabelową w programie SQL Server?


Odpowiedzi:


668

Zawartość

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 tempdb2 wierszach)

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

Lokalizacja logiczna

@table_variableszachowują się bardziej, jakby były częścią bieżącej bazy danych niż #temptabele. 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 #temptabel 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 #temptabelach, 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_variablessą dostępne tylko w ramach partii i zakresu, w którym zostały zadeklarowane. #temp_tablessą dostępne w ramach partii potomnych (wyzwalacze zagnieżdżone, procedura, execwywołania). #temp_tablesutworzone 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ć ##temptabele globalne ).

Dożywotni

@table_variablessą tworzone niejawnie, gdy partia zawierająca DECLARE @.. TABLEinstrukcję 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 DECLAREinstrukcją, niejawne tworzenie można zobaczyć poniżej.

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

--Works fine
SELECT *
FROM @T

#temp_tablessą tworzone jawnie po CREATE TABLEnapotkaniu instrukcji TSQL i mogą być upuszczone jawnie z DROP TABLElub 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_tablesale których ograniczenia i @table_variablestak zapobiegają. Narzut związany z konserwacją #temptabel 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źć #temptabelę, 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 TABLEinstrukcji. W przypadku zmiennych tabeli object_idfunkcja 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 PAGEo 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_variablessą przeprowadzane jako transakcje systemowe, niezależnie od jakiejkolwiek zewnętrznej transakcji użytkownika, podczas gdy równoważne #tempoperacje na tablicy byłyby wykonywane jako część samej transakcji użytkownika. Z tego powodu ROLLBACKpolecenie wpłynie na #temptabelę, ale pozostawi @table_variablenietknię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 tempdbdziennika 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

Widok szczegółowy

Zrzut ekranu z wynikami

Widok podsumowania (obejmuje rejestrowanie niejawnego upuszczenia i systemowe tabele podstawowe)

Zrzut ekranu z wynikami

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 #temptabelami 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 #temptabel zapobiegnie obcinaniu dziennika, tempdbpodczas gdy transakcje autonomiczne spawn dla zmiennych tabeli nie.

Zmienne tabel nie obsługują, TRUNCATEwię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 rowszliczanie 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_variablezapełnieniu, zostanie ona użyta zamiast tego dla liczności tabeli (Może się tak zdarzyć z powodu jawnego recompilelub 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.

Pokazuje dokładną liczbę wierszy

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 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 PLANwskazówką może być używany do ustawiania RT na #tempstołach takie same jak dla stałych tablicach.

Efektem tego wszystkiego jest to, że często plany wykonania generowane dla #temptabel są o rząd wielkości większe niż w przypadku, @table_variablesgdy 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 TableCardinalityprzy 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 #tempstołó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 SELECTze zmiennych tabeli Paul White wskazuje w komentarzach, że automatycznie pochodzą one z niejawną NOLOCKwskazówką. Pokazano to poniżej

DECLARE @T TABLE(X INT); 

SELECT X
FROM @T 
OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8607)

Wynik

*** 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 #tempstołu jest prawie tak lekki, że usuwa Sblokadę poziomu obiektu . NOLOCKWskazówkę lub READ UNCOMMITTEDizolacja poziom może być oczywiście wyraźnie określone podczas pracy z #temptabelami, 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 #temptabel. 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_locksjest blokada współdzielona w DATABASE.

Po odkomentowaniu BEGIN TRAN ... ROLLBACKzwracane 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 NONCLUSTEREDi 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_optionw deklaracji ograniczeń można określić różne s, w tym DATA_COMPRESSION, IGNORE_DUP_KEYi 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ą INCLUDEd kolumn, filtrowanych indeksów (do 2016 r.) Ani partycjonowania, #temptabele 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_variablesnie mogą mieć planu równoległego, #temp_tablesnie są w ten sposób ograniczone.

Widoczne jest obejście tego przepisywania w następujący sposób, który pozwala na SELECTró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_tablesnie można używać wewnątrz funkcji. @table_variablesmoże być używany wewnątrz skalarnych lub wielowymiarowych tabel UDF.
  • @table_variables nie mógł mieć nazwanych ograniczeń.
  • @table_variablesnie może być SELECT-ed INTO, ALTER-ed, TRUNCATEd ani być celem DBCCpoleceń takich jak DBCC CHECKIDENTlub lub SET IDENTITY INSERTi 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.
  • Zmienne tabelowe nie wydają się kwalifikować do optymalizacji udostępniania zestawu wierszy, co oznacza, że ​​usuwanie i aktualizowanie planów w stosunku do nich może napotkać więcej narzutu i PAGELATCH_EXczeka. ( 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 tempdbpliku 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 250na 251zacząłem widzieć napisy jak poniżej.

ProcMon

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 

Wyniki

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 memoryustawionymi 2000 MBi DBCC MEMORYSTATUSraportują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 #temptabela 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ć.

Strony w puli buforów


Przekonałem się, że SQL Server uzyskuje znacznie więcej zatrzasków podczas tworzenia tabel tymczasowych (nawet z buforowaniem) w porównaniu do równoważnej zmiennej tabelowej. Możesz przetestować, używając XE latch_acquired debugowania i tworząc tabelę z około 35 kolumnami. Znalazłem zmienną tabelkową, która zajmuje 4 zatrzaski, a tabelę temperaturową - około 70 zatrzasków.
Joe Obbish

40

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.

  1. #temp tabele domyślnie używają domyślnego sortowania instancji SQL Server. Więc jeśli nie określono inaczej, możesz napotkać problemy podczas porównywania lub aktualizacji wartości między tabelami #temp i tabelami bazy danych, jeśli masterdb ma inne zestawienie niż baza danych. Zobacz: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. Całkowicie oparta na osobistych doświadczeniach wydaje się, że dostępna pamięć ma wpływ na to, co działa lepiej. MSDN zaleca stosowanie zmiennych tabeli do przechowywania mniejszych zestawów wyników, ale przez większość czasu różnica nie jest nawet zauważalna. Jednak w większych zestawach w niektórych przypadkach stało się oczywiste, że zmienne w tabeli wymagają dużo więcej pamięci i mogą spowolnić zapytanie do przeszukiwania.

6
Należy również pamiętać, że sortowanie w tabelach #temp może dziedziczyć sortowanie wywołującej bazy danych, jeśli używasz programu SQL Server 2012 i baza danych jest zawarta.
Aaron Bertrand

Wyjaśnienie nr 2 dla małych i dużych zestawów stackoverflow.com/a/14465163/5224021
GibralterTop
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.