Dlaczego w tym konkretnym przypadku użycie zmiennej tabeli jest ponad dwa razy szybsze niż tabeli #temp?

37

Patrzyłem na artykuł tutaj Tabele tymczasowe vs. zmienne tabel i ich wpływ na wydajność programu SQL Server, a na serwerze SQL Server 2008 udało się odtworzyć wyniki podobne do pokazanych tam w 2005 roku.

Podczas wykonywania procedur przechowywanych (definicje poniżej) z tylko 10 wierszami wersja zmiennej tabeli wykonuje wersję tabeli tymczasowej ponad dwa razy.

Wyczyściłem pamięć podręczną procedur i uruchomiłem obie procedury przechowywane 10 000 razy, a następnie powtórzyłem proces dla kolejnych 4 uruchomień. Wyniki poniżej (czas w ms na partię)

T2_Time     V2_Time
----------- -----------
8578        2718      
6641        2781    
6469        2813   
6766        2797
6156        2719

Moje pytanie brzmi: jaka jest przyczyna lepszej wydajności wersji zmiennej tabeli?

Przeprowadziłem dochodzenie. np. Patrząc na liczniki wydajności za pomocą

SELECT cntr_value
from sys.dm_os_performance_counters
where counter_name = 'Temp Tables Creation Rate';

potwierdza, że ​​w obu przypadkach obiekty tymczasowe są buforowane po pierwszym uruchomieniu zgodnie z oczekiwaniami, a nie tworzone od nowa dla każdego wywołania.

Podobnie śledzenie Auto Stats, SP:Recompile, SQL:StmtRecompilewydarzenia w Profiler (zrzut ekranu poniżej) pokazuje, że wydarzenia te występują tylko raz (na pierwszym wezwaniem #temptabeli procedury przechowywanej), a pozostałe 9,999 egzekucje nie budzą żadnej z tych wydarzeń. (Wersja zmiennej tabeli nie otrzymuje żadnego z tych zdarzeń)

Ślad

Nieco większy narzut związany z pierwszym uruchomieniem procedury przechowywanej nie może w żaden sposób uwzględniać dużej ogólnej różnicy, ponieważ nadal zajmuje tylko kilka ms, aby wyczyścić pamięć podręczną procedur i uruchomić obie procedury raz, więc nie wierzę w statystyki ani rekompilacje mogą być przyczyną.

Utwórz wymagane obiekty bazy danych

CREATE DATABASE TESTDB_18Feb2012;

GO

USE TESTDB_18Feb2012;

CREATE TABLE NUM 
  ( 
     n INT PRIMARY KEY, 
     s VARCHAR(128) 
  ); 

WITH NUMS(N) 
     AS (SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY $/0) 
         FROM   master..spt_values v1, 
                master..spt_values v2) 
INSERT INTO NUM 
SELECT N, 
       'Value: ' + CONVERT(VARCHAR, N) 
FROM   NUMS 

GO

CREATE PROCEDURE [dbo].[T2] @total INT 
AS 
  CREATE TABLE #T 
    ( 
       n INT PRIMARY KEY, 
       s VARCHAR(128) 
    ) 

  INSERT INTO #T 
  SELECT n, 
         s 
  FROM   NUM 
  WHERE  n%100 > 0 
         AND n <= @total 

  DECLARE @res VARCHAR(128) 

  SELECT @res = MAX(s) 
  FROM   NUM 
  WHERE  n <= @total 
         AND NOT EXISTS(SELECT * 
                        FROM   #T 
                        WHERE  #T.n = NUM.n) 
GO

CREATE PROCEDURE [dbo].[V2] @total INT 
AS 
  DECLARE @V TABLE ( 
    n INT PRIMARY KEY, 
    s VARCHAR(128)) 

  INSERT INTO @V 
  SELECT n, 
         s 
  FROM   NUM 
  WHERE  n%100 > 0 
         AND n <= @total 

  DECLARE @res VARCHAR(128) 

  SELECT @res = MAX(s) 
  FROM   NUM 
  WHERE  n <= @total 
         AND NOT EXISTS(SELECT * 
                        FROM   @V V 
                        WHERE  V.n = NUM.n) 


GO

Skrypt testowy

SET NOCOUNT ON;

DECLARE @T1 DATETIME2,
        @T2 DATETIME2,
        @T3 DATETIME2,  
        @Counter INT = 0

SET @T1 = SYSDATETIME()

WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.T2 10
SET @Counter += 1
END

SET @T2 = SYSDATETIME()
SET @Counter = 0

WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.V2 10
SET @Counter += 1
END

SET @T3 = SYSDATETIME()

SELECT DATEDIFF(MILLISECOND,@T1,@T2) AS T2_Time,
       DATEDIFF(MILLISECOND,@T2,@T3) AS V2_Time
Martin Smith
źródło
Śledzenie profilera wskazuje, że statystyki są tworzone w #temptabeli tylko raz, mimo że zostały wyczyszczone i ponownie wypełnione kolejne 9 999 razy po tym.
Martin Smith

Odpowiedzi:

31

Wyjście SET STATISTICS IO ONdla obu wygląda podobnie

SET STATISTICS IO ON;
PRINT 'V2'
EXEC dbo.V2 10
PRINT 'T2'
EXEC dbo.T2 10

Daje

V2
Table '#58B62A60'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

Table '#58B62A60'. Scan count 10, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

T2
Table '#T__ ... __00000000E2FE'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

Table '#T__ ... __00000000E2FE'. Scan count 0, logical reads 20
Table 'NUM'. Scan count 1, logical reads 3

I jak Aaron zwraca uwagę w komentarzach plan dla wersji zmiennej tabela jest rzeczywiście mniej skuteczny jak podczas gdy oba mają zagnieżdżone pętle PLAN napędzany przez indeks szukać na dbo.NUMtych #temppreform Wersja przedstawić dążą do indeksu na [#T].n = [dbo].[NUM].[n]z pozostałego orzecznika [#T].[n]<=[@total]natomiast zmiennej tabeli wersja wykonuje wyszukiwanie indeksu @V.n <= [@total]z pozostałym predykatem @V.[n]=[dbo].[NUM].[n]i przetwarza więcej wierszy (dlatego ten plan działa tak słabo dla większej liczby wierszy)

Użycie Rozszerzonych zdarzeń do sprawdzenia typów oczekiwania dla określonego pająka daje te wyniki dla 10.000 wykonańEXEC dbo.T2 10

+---------------------+------------+----------------+----------------+----------------+
|                     |            |     Total      | Total Resource |  Total Signal  |
| Wait Type           | Wait Count | Wait Time (ms) | Wait Time (ms) | Wait Time (ms) |
+---------------------+------------+----------------+----------------+----------------+
| SOS_SCHEDULER_YIELD | 16         | 19             | 19             | 0              |
| PAGELATCH_SH        | 39998      | 14             | 0              | 14             |
| PAGELATCH_EX        | 1          | 0              | 0              | 0              |
+---------------------+------------+----------------+----------------+----------------+

i te wyniki dla 10.000 egzekucji EXEC dbo.V2 10

+---------------------+------------+----------------+----------------+----------------+
|                     |            |     Total      | Total Resource |  Total Signal  |
| Wait Type           | Wait Count | Wait Time (ms) | Wait Time (ms) | Wait Time (ms) |
+---------------------+------------+----------------+----------------+----------------+
| PAGELATCH_EX        | 2          | 0              | 0              | 0              |
| PAGELATCH_SH        | 1          | 0              | 0              | 0              |
| SOS_SCHEDULER_YIELD | 676        | 0              | 0              | 0              |
+---------------------+------------+----------------+----------------+----------------+

Jest więc jasne, że liczba PAGELATCH_SHoczekujących jest znacznie wyższa w #tempprzypadku tabeli. Nie znam żadnego sposobu dodania zasobu oczekiwania do śledzenia zdarzeń rozszerzonych, więc aby to zbadać, uruchomiłem

WHILE 1=1
EXEC dbo.T2 10

Podczas gdy w innym połączeniu odpytywanie sys.dm_os_waiting_tasks

CREATE TABLE #T(resource_description NVARCHAR(2048))

WHILE 1=1
INSERT INTO #T
SELECT resource_description
FROM sys.dm_os_waiting_tasks
WHERE session_id=<spid_of_other_session> and wait_type='PAGELATCH_SH'

Po pozostawieniu go na około 15 sekund uzyskał następujące wyniki

+-------+----------------------+
| Count | resource_description |
+-------+----------------------+
|  1098 | 2:1:150              |
|  1689 | 2:1:146              |
+-------+----------------------+

Obie zatrzaśnięte strony należą do (różnych) nieklastrowanych indeksów w tempdb.sys.sysschobjstabeli podstawowej o nazwach 'nc1'i 'nc2'.

Zapytanie tempdb.sys.fn_dblogpodczas przebiegów wskazuje, że liczba rekordów dziennika dodanych przy pierwszym wykonaniu każdej procedury składowanej była nieco zmienna, ale dla kolejnych wykonań liczba dodana przy każdej iteracji była bardzo spójna i przewidywalna. Po buforowaniu planów procedur liczba wpisów w dzienniku jest o połowę mniejsza niż w przypadku #tempwersji.

+-----------------+----------------+------------+
|                 | Table Variable | Temp Table |
+-----------------+----------------+------------+
| First Run       |            126 | 72 or 136  |
| Subsequent Runs |             17 | 32         |
+-----------------+----------------+------------+

Patrząc na wpisy dziennika transakcji bardziej szczegółowo dla #tempwersji tabelowej SP każde kolejne wywołanie procedury składowanej tworzy trzy transakcje, a zmienna tabelowa tylko dwie.

+---------------------------------+----+---------------------------------+----+
|           #Temp Table                |         @Table Variable              |
+---------------------------------+----+---------------------------------+----+
| CREATE TABLE                    |  9 |                                 |    |
| INSERT                          | 12 | TVQuery                         | 12 |
| FCheckAndCleanupCachedTempTable | 11 | FCheckAndCleanupCachedTempTable |  5 |
+---------------------------------+----+---------------------------------+----+

W INSERT/ TVQUERYtransakcje są identyczne z wyjątkiem nazwy. Zawiera rekordy dziennika dla każdego z 10 wierszy wstawionych do tabeli tymczasowej lub zmiennej tabeli oraz wpisy LOP_BEGIN_XACT/ LOP_COMMIT_XACT.

CREATE TABLETransakcja pojawia się tylko w #Tempwersji i wygląda następująco.

+-----------------+-------------------+---------------------+
|    Operation    |      Context      |    AllocUnitName    |
+-----------------+-------------------+---------------------+
| LOP_BEGIN_XACT  | LCX_NULL          |                     |
| LOP_SHRINK_NOOP | LCX_NULL          |                     |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc1  |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc1  |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc2  |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc2  |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst |
| LOP_COMMIT_XACT | LCX_NULL          |                     |
+-----------------+-------------------+---------------------+

FCheckAndCleanupCachedTempTableTransakcja pojawia się w obu, ale ma 6 dodatkowych wpisów w #tempwersji. Są to 6 wierszy, które odnoszą się sys.sysschobjsi mają dokładnie taki sam wzór jak powyżej.

+-----------------+-------------------+----------------------------------------------+
|    Operation    |      Context      |                AllocUnitName                 |
+-----------------+-------------------+----------------------------------------------+
| LOP_BEGIN_XACT  | LCX_NULL          |                                              |
| LOP_DELETE_ROWS | LCX_NONSYS_SPLIT  | dbo.#7240F239.PK__#T________3BD0199374293AAB |
| LOP_HOBT_DELTA  | LCX_NULL          |                                              |
| LOP_HOBT_DELTA  | LCX_NULL          |                                              |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst                          |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc1                           |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc1                           |
| LOP_DELETE_ROWS | LCX_MARK_AS_GHOST | sys.sysschobjs.nc2                           |
| LOP_INSERT_ROWS | LCX_INDEX_LEAF    | sys.sysschobjs.nc2                           |
| LOP_MODIFY_ROW  | LCX_CLUSTERED     | sys.sysschobjs.clst                          |
| LOP_COMMIT_XACT | LCX_NULL          |                                              |
+-----------------+-------------------+----------------------------------------------+

Patrząc na te 6 wierszy w obu transakcjach, odpowiadają one tym samym operacjom. Pierwsza LOP_MODIFY_ROW, LCX_CLUSTEREDto aktualizacja modify_datekolumny w sys.objects. Pozostałe pięć wierszy dotyczy zmiany nazwy obiektu. Ponieważ namejest to kluczowa kolumna obu dotkniętych NCI ( nc1i nc2), jest to wykonywane jako usuwanie / wstawianie dla tych, a następnie wraca do indeksu klastrowego i również to aktualizuje.

Wydaje się, że w przypadku #tempwersji tabelowej, gdy procedura przechowywana kończy część czyszczenia przeprowadzonego przez FCheckAndCleanupCachedTempTabletransakcję, jest zmiana nazwy tabeli tymczasowej z czegoś podobnego #T__________________________________________________________________________________________________________________00000000E316na inną nazwę wewnętrzną, taką jak #2F4A0079i kiedy jest wprowadzana, CREATE TABLEtransakcja zmienia jej nazwę z powrotem. Ta nazwa flip-flop może być widoczna w jednym połączeniu wykonującym się dbo.T2w pętli, podczas gdy w innym

WHILE 1=1
SELECT name, object_id, create_date, modify_date
FROM tempdb.sys.objects 
WHERE name LIKE '#%'

Przykładowe wyniki

Zrzut ekranu

Jednym z potencjalnych wyjaśnień obserwowanej różnicy wydajności, na które powołał się Alex, jest to, że odpowiedzialna jest ta dodatkowa praca tempdbpolegająca na utrzymaniu tabel systemowych .


Po uruchomieniu obu procedur w pętli profiler programu Visual Studio Code ujawnia następujące informacje

+-------------------------------+--------------------+-------+-----------+
|           Function            |    Explanation     | Temp  | Table Var |
+-------------------------------+--------------------+-------+-----------+
| CXStmtDML::XretExecute        | Insert ... Select  | 16.93 | 37.31     |
| CXStmtQuery::ErsqExecuteQuery | Select Max         | 8.77  | 23.19     |
+-------------------------------+--------------------+-------+-----------+
| Total                         |                    | 25.7  | 60.5      |
+-------------------------------+--------------------+-------+-----------+

Wersja zmiennej tabeli spędza około 60% czasu na wykonywaniu instrukcji insert i późniejszego wyboru, podczas gdy tabela tymczasowa jest mniejsza o połowę. Jest to zgodne z harmonogramami pokazanymi w PO i powyższym wnioskiem, że różnica w wydajności zależy od czasu poświęconego na wykonywanie pracy pomocniczej, a nie z powodu czasu poświęconego na wykonanie samego zapytania.

Najważniejszymi funkcjami przyczyniającymi się do „braku” 75% w tymczasowej wersji tabeli są

+------------------------------------+-------------------+
|              Function              | Inclusive Samples |
+------------------------------------+-------------------+
| CXStmtCreateTableDDL::XretExecute  | 26.26%            |
| CXStmtDDL::FinishNormalImp         | 4.17%             |
| TmpObject::Release                 | 27.77%            |
+------------------------------------+-------------------+
| Total                              | 58.20%            |
+------------------------------------+-------------------+

W obu funkcjach tworzenia i zwalniania CMEDProxyObject::SetNamepokazana jest funkcja z włączającą wartością próbki 19.6%. Z czego wywnioskuję, że 39,2% czasu w przypadku tabeli tymczasowej zajmowane jest przez zmianę nazwy opisaną wcześniej.

Największe w wersji zmiennej stołowej, które przyczyniają się do pozostałych 40%, to

+-----------------------------------+-------------------+
|             Function              | Inclusive Samples |
+-----------------------------------+-------------------+
| CTableCreate::LCreate             | 7.41%             |
| TmpObject::Release                | 12.87%            |
+-----------------------------------+-------------------+
| Total                             | 20.28%            |
+-----------------------------------+-------------------+

Profil stołu tymczasowego

wprowadź opis zdjęcia tutaj

Profil zmiennej tabeli

wprowadź opis zdjęcia tutaj

Martin Smith
źródło
10

Dyskoteka inferno

Ponieważ jest to starsze pytanie, postanowiłem ponownie sprawdzić problem w nowszych wersjach programu SQL Server, aby sprawdzić, czy ten sam profil wydajności nadal istnieje lub czy w ogóle zmieniły się właściwości.

W szczególności dodanie tabel systemowych w pamięci dla SQL Server 2019 wydaje się być dobrą okazją do ponownego przetestowania.

Używam nieco innej uprzęży testowej, ponieważ napotkałem ten problem podczas pracy nad czymś innym.

Testowanie, testowanie

Korzystając z wersji Stack Overflow 2013 , mam ten indeks i te dwie procedury:

Indeks:

CREATE INDEX ix_whatever 
    ON dbo.Posts(OwnerUserId) INCLUDE(Score);
GO

Tabela temperatur:

    CREATE OR ALTER PROCEDURE dbo.TempTableTest(@Id INT)
    AS
    BEGIN
    SET NOCOUNT ON;

        CREATE TABLE #t(i INT NOT NULL);
        DECLARE @i INT;

        INSERT #t ( i )
        SELECT p.Score
        FROM dbo.Posts AS p
        WHERE p.OwnerUserId = @Id;

        SELECT @i = AVG(t.i)
        FROM #t AS t;

    END;
    GO 

Zmienna tabeli:

    CREATE OR ALTER PROCEDURE dbo.TableVariableTest(@Id INT)
    AS
    BEGIN
    SET NOCOUNT ON;

        DECLARE @t TABLE (i INT NOT NULL);
        DECLARE @i INT;

        INSERT @t ( i )
        SELECT p.Score
        FROM dbo.Posts AS p
        WHERE p.OwnerUserId = @Id;

        SELECT @i = AVG(t.i)
        FROM @t AS t;

    END;
    GO 

Aby zapobiec potencjalnym oczekiwaniom ASYNC_NETWORK_IO , używam procedur otoki.

CREATE PROCEDURE #TT AS
SET NOCOUNT ON;
    DECLARE @i INT = 1;
    DECLARE @StartDate DATETIME2(7) = SYSDATETIME();

    WHILE @i <= 50000
        BEGIN
            EXEC dbo.TempTableTest @Id = @i;
            SET @i += 1;
        END;
    SELECT DATEDIFF(MILLISECOND, @StartDate, SYSDATETIME()) AS [ElapsedTimeMilliseconds];
GO

CREATE PROCEDURE #TV AS
SET NOCOUNT ON;
    DECLARE @i INT = 1;
    DECLARE @StartDate DATETIME2(7) = SYSDATETIME();

    WHILE @i <= 50000
        BEGIN
            EXEC dbo.TableVariableTest @Id = @i;
            SET @i += 1;
        END;
    SELECT DATEDIFF(MILLISECOND, @StartDate, SYSDATETIME()) AS [ElapsedTimeMilliseconds];
GO

SQL Server 2017

Ponieważ 2014 i 2016 są w zasadzie RELIKA w tym momencie, zaczynam testowanie od 2017 roku. Ponadto, dla zwięzłości, skaczę od razu do profilowania kodu za pomocą Perfview . W prawdziwym życiu patrzyłem na oczekiwania, zatrzaski, spinlocki, szalone flagi ze śladami i inne rzeczy.

Profilowanie kodu jest jedyną rzeczą, która ujawniła coś interesującego.

Różnica czasu:

  • Tabela temperatur: 17891 ms
  • Zmienna tabeli: 5891 ms

Wciąż bardzo wyraźna różnica, co? Ale co teraz uderza w SQL Server?

ORZECHY

Patrząc na dwa najwyższe wzrosty w różnych próbkach, widzimy sqlmini sqlsqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucketjesteśmy dwoma największymi przestępcami.

ORZECHY

Sądząc po nazwach w stosach wywołań, czyszczenie i zmiana nazw wewnętrznych tabel tymczasowych wydaje się być największym czasem do wyczerpania wywołania tabeli tymczasowej w porównaniu do wywołania zmiennej tabelowej.

Mimo że zmienne tabel są wewnętrznie wspierane przez tabele tymczasowe, nie wydaje się to problemem.

SET STATISTICS IO ON;
DECLARE @t TABLE(id INT);
SELECT * FROM @t AS t;

Tabela „# B98CE339”. Liczba skanów 1

Przeglądanie stosów połączeń dla testu zmiennej tabeli w ogóle nie pokazuje żadnego z głównych przestępców:

ORZECHY

SQL Server 2019 (wanilia)

W porządku, więc to nadal jest problem w SQL Server 2017, czy coś nowego w 2019 roku jest gotowe?

Po pierwsze, aby pokazać, że nie ma nic w rękawie:

SELECT c.name,
       c.value_in_use,
       c.description
FROM sys.configurations AS c
WHERE c.name = 'tempdb metadata memory-optimized';

ORZECHY

Różnica czasu:

  • Tabela temperatur: 15765 ms
  • Zmienna tabeli: 7250 ms

Obie procedury były różne. Wywołanie tabeli temp było o kilka sekund szybsze, a wywołanie zmiennej tabeli było o około 1,5 sekundy wolniejsze. Spowolnienie zmiennej stołowej można częściowo wyjaśnić kompilacją odroczoną zmiennej stołowej , nowym wyborem optymalizatora w 2019 r.

Patrząc na różnicę w Perfview, to się nieco zmieniło - nie ma już sqlmin - ale sqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucketjest.

ORZECHY

SQL Server 2019 (tabele systemowe Tempdb w pamięci)

Co z tą nową rzeczą w tabeli systemowej pamięci? Hm? Sup z tym?

Włączmy to!

EXEC sys.sp_configure @configname = 'advanced', 
                      @configvalue = 1  
RECONFIGURE;

EXEC sys.sp_configure @configname = 'tempdb metadata memory-optimized', 
                      @configvalue = 1 
RECONFIGURE;

Zauważ, że wymaga to ponownego uruchomienia SQL Server, aby się uruchomić, więc wybacz mi, gdy ponownie uruchamiam SQL w to piękne piątek po południu.

Teraz sprawy wyglądają inaczej:

SELECT c.name,
       c.value_in_use,
       c.description
FROM sys.configurations AS c
WHERE c.name = 'tempdb metadata memory-optimized';

SELECT *, 
       OBJECT_NAME(object_id) AS object_name, 
       @@VERSION AS sql_server_version
FROM tempdb.sys.memory_optimized_tables_internal_attributes;

ORZECHY

Różnica czasu:

  • Tabela temperatur: 11638 ms
  • Zmienna tabeli: 7403 ms

Tabele temp były o około 4 sekundy lepsze! To jest coś.

Coś mi się podoba

Tym razem diff Perfview nie jest zbyt interesujący. Obok siebie warto zauważyć, jak blisko są czasy na planszy:

ORZECHY

Ciekawym punktem w diff są wywołania hkengine!, które mogą wydawać się oczywiste, ponieważ funkcje hekaton-ish są teraz w użyciu.

ORZECHY

Jeśli chodzi o dwa pierwsze elementy w diff, nie mogę zrobić wiele z ntoskrnl!?:

ORZECHY

Lub sqltses!CSqlSortManager_80::GetSortKey, ale są tutaj, aby Smrtr Ppl ™ mógł zobaczyć:

ORZECHY

Zauważ, że istnieje nieudokumentowana i zdecydowanie nie bezpieczna dla produkcji, więc nie używaj jej flagi śledzenia uruchamiania, której możesz użyć, aby mieć dodatkowe obiekty systemowe tabeli temp (sysrowsets, sysallocunits i sysseobjvalues) zawarte w funkcji w pamięci, ale to w tym przypadku nie zrobiło zauważalnej różnicy w czasach wykonania.

Łapanka

Nawet w nowszych wersjach serwera SQL wywołania wysokiej częstotliwości do zmiennych tabeli są znacznie szybsze niż wywołania wysokiej częstotliwości do tabel temp.

Chociaż kusi, by obwiniać kompilacje, rekompilacje, statystyki automatyczne, zatrzaski, blokady, buforowanie lub inne problemy, problem nadal dotyczy zarządzania czyszczeniem tabeli temp.

Jest to bliższe wywołanie w SQL Server 2019 z włączonymi tabelami systemowymi w pamięci, ale zmienne tabel nadal działają lepiej, gdy częstotliwość połączeń jest wysoka.

Oczywiście jako mędrzec vaping po przemyśleniu: „używaj zmiennych tabeli, gdy wybór planu nie jest problemem”.

Erik Darling
źródło
Fajnie - przepraszam, że tęskniłem za dodaniem odpowiedzi, dopóki nie podążyłem za linkiem w swoim blogu „debugowanie”
Martin Smith,