Warum ist eine Tabellenvariable in diesem speziellen Fall mehr als doppelt so schnell wie eine #temp-Tabelle?

37

Ich habe mir den Artikel hier angesehen. Temporäre Tabellen im Vergleich zu Tabellenvariablen und ihre Auswirkungen auf die SQL Server-Leistung. In SQL Server 2008 konnten ähnliche Ergebnisse wie dort für 2005 erzielt werden.

Wenn die gespeicherten Prozeduren (Definitionen unten) mit nur 10 Zeilen ausgeführt werden, führt die Tabellenvariablenversion out die temporäre Tabellenversion mehr als zweimal aus.

Ich habe den Prozedur-Cache geleert und die beiden gespeicherten Prozeduren 10.000 Mal ausgeführt. Anschließend habe ich den Vorgang für weitere 4 Läufe wiederholt. Ergebnisse unten (Zeit in ms pro Charge)

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

Meine Frage ist: Was ist der Grund für die bessere Leistung der Tabellenvariablenversion?

Ich habe Nachforschungen angestellt. zB Betrachten der Leistungsindikatoren mit

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

Bestätigt, dass in beiden Fällen die temporären Objekte nach der ersten Ausführung wie erwartet zwischengespeichert werden, anstatt bei jedem Aufruf erneut von Grund auf neu erstellt zu werden.

Ähnlich Verfolgen der Auto Stats, SP:Recompile, SQL:StmtRecompileEreignisse in Profiler (Abbildung unten) zeigt , dass diese Ereignisse nur einmal vorkommen (auf dem ersten Aufruf der #tempTabelle gespeicherten Prozedur) und die anderen 9.999 Hinrichtungen nicht eines dieser Ereignisse erhöhen. (Die Tabellenvariablenversion erhält keines dieser Ereignisse.)

Spur

Der geringfügig höhere Overhead des ersten Durchlaufs der gespeicherten Prozedur kann jedoch in keiner Weise den großen Gesamtunterschied ausmachen, da das Löschen des Prozedurcaches und das einmalige Ausführen beider Prozeduren immer noch nur wenige ms dauern, sodass ich weder Statistiken noch Statistiken glaube Neukompilierungen können die Ursache sein.

Erstellen Sie die erforderlichen Datenbankobjekte

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

Skript testen

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
quelle
Die Profiler-Ablaufverfolgung gibt an, dass Statistiken für die #tempTabelle nur einmal erstellt werden, obwohl sie anschließend 9.999 Mal gelöscht und neu aufgefüllt wurden.
Martin Smith

Antworten:

31

Die Ausgabe von SET STATISTICS IO ONfür beide sieht ähnlich aus

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

Gibt

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

Und als Aaron ist der Plan für die Tabellenvariable Version in den Kommentaren weist darauf hin , tatsächlich weniger effizient als während beide hat eine verschachtelte Schleife durch einen Index sucht angetrieben plant dbo.NUMdie #tempTabelle Version führt ein in den Index auf suchen [#T].n = [dbo].[NUM].[n]mit Rest Prädikat [#T].[n]<=[@total]während des Tabellenvariable Die Version führt eine Indexsuche @V.n <= [@total]mit einem verbleibenden Prädikat durch @V.[n]=[dbo].[NUM].[n]und verarbeitet so mehr Zeilen (weshalb dieser Plan für eine größere Anzahl von Zeilen so schlecht abschneidet).

Wenn Sie Extended Events verwenden , um die Wartetypen für die spezifische Spid zu untersuchen, erhalten Sie diese Ergebnisse für 10.000 Ausführungen vonEXEC 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              |
+---------------------+------------+----------------+----------------+----------------+

und diese Ergebnisse für 10.000 Hinrichtungen von 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              |
+---------------------+------------+----------------+----------------+----------------+

Es ist also klar, dass die Anzahl der PAGELATCH_SHWartezeiten in der #tempTabelle viel höher ist. Mir ist keine Möglichkeit bekannt, die Wait-Ressource zum erweiterten Ereignistrace hinzuzufügen, um dies weiter zu untersuchen

WHILE 1=1
EXEC dbo.T2 10

Während eines anderen Verbindungsabrufs 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'

Nachdem es etwa 15 Sekunden lang laufen gelassen worden war, hatte es die folgenden Ergebnisse gesammelt

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

Beide Seiten, die zwischengespeichert werden, gehören zu (verschiedenen) nicht gruppierten Indizes für die Basistabelle mit den tempdb.sys.sysschobjsNamen 'nc1'und 'nc2'.

Das Abfragen tempdb.sys.fn_dblogwährend der Ausführung zeigt an, dass die Anzahl der Protokolldatensätze, die bei der ersten Ausführung jeder gespeicherten Prozedur hinzugefügt wurden, etwas variabel war. Für nachfolgende Ausführungen war die Anzahl, die bei jeder Iteration hinzugefügt wurde, jedoch sehr konsistent und vorhersehbar. Sobald die Prozedurpläne zwischengespeichert sind, beträgt die Anzahl der Protokolleinträge ungefähr die Hälfte der für die #tempVersion erforderlichen .

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

Wenn Sie die Transaktionsprotokolleinträge für die #tempTabellenversion des SP genauer betrachten, werden bei jedem nachfolgenden Aufruf der gespeicherten Prozedur drei Transaktionen und bei der Tabellenvariablen nur zwei Transaktionen erstellt.

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

Die INSERT/ TVQUERY-Transaktionen sind bis auf den Namen identisch. Dieser enthält die Protokollsätze für jede der 10 Zeilen, die in die temporäre Tabelle oder Tabellenvariable eingefügt wurden, sowie die Einträge LOP_BEGIN_XACT/ LOP_COMMIT_XACT.

Die CREATE TABLETransaktion erscheint nur in der #TempVersion und sieht wie folgt aus.

+-----------------+-------------------+---------------------+
|    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          |                     |
+-----------------+-------------------+---------------------+

Die FCheckAndCleanupCachedTempTableTransaktion erscheint in beiden, hat aber 6 zusätzliche Einträge in der #tempVersion. Dies sind die 6 Zeilen, auf die Bezug genommen wird, sys.sysschobjsund sie haben genau das gleiche Muster wie oben.

+-----------------+-------------------+----------------------------------------------+
|    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          |                                              |
+-----------------+-------------------+----------------------------------------------+

Betrachtet man diese 6 Zeilen in beiden Transaktionen, so entsprechen sie denselben Operationen. Das erste LOP_MODIFY_ROW, LCX_CLUSTEREDist eine Aktualisierung der modify_dateSpalte in sys.objects. Die verbleibenden fünf Zeilen befassen sich alle mit dem Umbenennen von Objekten. Da namees sich um eine Schlüsselspalte der beiden betroffenen NCIs ( nc1und nc2) handelt, wird dies als Löschen / Einfügen für diese ausgeführt. Dann wird zum Clustered-Index zurückgekehrt und auch dieser aktualisiert.

Es scheint, dass für die #tempTabellenversion, wenn die gespeicherte Prozedur einen Teil der von der FCheckAndCleanupCachedTempTableTransaktion ausgeführten Bereinigung beendet , die temporäre Tabelle von so etwas wie #T__________________________________________________________________________________________________________________00000000E316einem anderen internen Namen umbenannt wird, #2F4A0079und wenn sie eingegeben wird CREATE TABLE, benennt die Transaktion sie zurück. Dieser Flip-Flop-Name kann von einer Verbindung gesehen werden, die dbo.T2in einer Schleife ausgeführt wird, während sie in einer anderen ausgeführt wird

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

Beispiel Ergebnisse

Bildschirmfoto

Eine mögliche Erklärung für das beobachtete Leistungsgefälle, auf das Alex anspielt, ist, dass diese zusätzliche Arbeit, die die Systemtabellen verwaltet tempdb, dafür verantwortlich ist.


Wenn Sie beide Prozeduren in einer Schleife ausführen, wird im Visual Studio Code-Profiler Folgendes angezeigt

+-------------------------------+--------------------+-------+-----------+
|           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      |
+-------------------------------+--------------------+-------+-----------+

Die Version der Tabellenvariablen verbringt etwa 60% der Zeit mit der Ausführung der Einfügeanweisung und der anschließenden Auswahl, während die temporäre Tabelle weniger als die Hälfte davon beträgt. Dies steht im Einklang mit den im OP angegebenen Zeitpunkten und der Schlussfolgerung, dass der Leistungsunterschied auf die Zeit zurückzuführen ist, die für die Ausführung von Nebenarbeiten aufgewendet wurde, und nicht auf die Zeit, die für die Ausführung der Abfrage selbst aufgewendet wurde.

Die wichtigsten Funktionen, die zu den "fehlenden" 75% in der temporären Tabellenversion beitragen, sind

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

Sowohl unter der Funktion create als auch unter der Funktion release wird die Funktion CMEDProxyObject::SetNamemit dem Beispielwert inclusive von angezeigt 19.6%. Daraus schließe ich, dass 39,2% der Zeit in der temporären Tabelle mit der zuvor beschriebenen Umbenennung belegt sind.

Und die größten in der Tabellenvariablenversion, die zu den anderen 40% beitragen, sind

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

Temporäres Tabellenprofil

Bildbeschreibung hier eingeben

Tabellenvariablenprofil

Bildbeschreibung hier eingeben

Martin Smith
quelle
10

Disco Inferno

Da dies eine ältere Frage ist, habe ich mich entschlossen, das Problem bei neueren Versionen von SQL Server erneut zu untersuchen, um festzustellen, ob das gleiche Leistungsprofil noch vorhanden ist oder ob sich die Merkmale überhaupt geändert haben.

Insbesondere das Hinzufügen von speicherinternen Systemtabellen für SQL Server 2019 scheint eine lohnende Gelegenheit für einen erneuten Test zu sein.

Ich verwende ein etwas anderes Testgeschirr, da ich bei der Arbeit an etwas anderem auf dieses Problem gestoßen bin.

Test, Test

Mit der Version 2013 von Stack Overflow habe ich diesen Index und diese beiden Verfahren:

Index:

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

Temp-Tabelle:

    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 

Tabellenvariable:

    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 

Um zu verhindern, dass ASYNC_NETWORK_IO möglicherweise wartet , verwende ich Wrapper-Prozeduren.

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

Da 2014 und 2016 zu diesem Zeitpunkt im Grunde genommen RELICS sind, beginne ich meine Tests mit 2017. Der Kürze halber springe ich direkt zum Profilieren des Codes mit Perfview . Im wirklichen Leben schaute ich mir Wartezeiten, Riegel, Spinlocks, verrückte Fahnen und andere Dinge an.

Das Profilieren des Codes ist das Einzige, das etwas Interessantes zutage gefördert hat.

Zeitunterschied:

  • Temporäre Tabelle: 17891 ms
  • Tabellenvariable: 5891 ms

Immer noch ein sehr deutlicher Unterschied, was? Aber was trifft SQL Server jetzt?

NÜSSE

Mit Blick auf die oberen zwei Erhöhungen der diffed Proben, wir sehen sqlminund sqlsqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucketsind die beiden größten Straftäter.

NÜSSE

Nach den Namen in den Aufruflisten zu urteilen, scheint das Aufräumen und interne Umbenennen von temporären Tabellen die größte Zeitverschwendung beim Aufruf von temporären Tabellen im Vergleich zum Aufruf von Tabellenvariablen zu sein.

Auch wenn Tabellenvariablen intern von temporären Tabellen gesichert werden, scheint dies kein Problem zu sein.

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

Tabelle '# B98CE339'. Scananzahl 1

Das Durchsuchen der Call-Stacks für den Tabellenvariablentest zeigt keinen der Haupttäter:

NÜSSE

SQL Server 2019 (Vanilla)

Okay, das ist also immer noch ein Problem in SQL Server 2017. Ist 2019 etwas anderes im Auslieferungszustand?

Erstens, um zu zeigen, dass nichts in meinem Ärmel ist:

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

NÜSSE

Zeitunterschied:

  • Temp-Tabelle: 15765 ms
  • Tabellenvariable: 7250 ms

Beide Verfahren waren unterschiedlich. Der temporäre Tabellenaufruf war einige Sekunden schneller, und der Tabellenvariablenaufruf war etwa 1,5 Sekunden langsamer. Die Verlangsamung der Tabellenvariablen kann teilweise durch die verzögerte Kompilierung der Tabellenvariablen erklärt werden , eine neue Optimierungsoptimierung im Jahr 2019.

Betrachtet man den Unterschied in Perfview, hat er sich etwas geändert - sqlmin ist nicht mehr da - sqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucketist es aber .

NÜSSE

SQL Server 2019 (In-Memory-Tempdb-Systemtabellen)

Was ist mit dieser neuen Tabelle für Speichersysteme? Hm? Sup damit?

Lass es uns einschalten!

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

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

Beachten Sie, dass dies einen Neustart von SQL Server erfordert. Entschuldigen Sie mich, während ich SQL an diesem schönen Freitagnachmittag neu starte.

Jetzt sieht es anders aus:

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;

NÜSSE

Zeitunterschied:

  • Temp-Tabelle: 11638 ms
  • Tabellenvariable: 7403 ms

Die temporären Tische machten ungefähr 4 Sekunden besser! Das ist etwas.

Ich mag etwas

Diesmal ist der Perfview Diff nicht sehr interessant. Nebeneinander ist es interessant festzustellen, wie eng die Zeiten auf der ganzen Linie sind:

NÜSSE

Ein interessanter Punkt im Diff sind die Aufrufe von hkengine!, was offensichtlich erscheinen mag, da jetzt hekatonische Funktionen verwendet werden.

NÜSSE

Was die ersten beiden Punkte im Diff angeht, kann ich nicht viel daraus machen ntoskrnl!?:

NÜSSE

Oder sqltses!CSqlSortManager_80::GetSortKey, aber sie sind hier, damit Smrtr Ppl ™ sich ansieht:

NÜSSE

Beachten Sie, dass es ein nicht dokumentiertes und definitiv nicht sicheres Produkt gibt. Verwenden Sie dieses Start-Trace-Flag daher nicht, um zusätzliche temporäre Tabellensystemobjekte (sysrowsets, sysallocunits und sysseobjvalues) in die speicherinterne Funktion aufzunehmen Die Ausführungszeiten haben sich in diesem Fall nicht bemerkbar gemacht.

Zusammenfassen

Selbst in neueren Versionen von SQL Server sind Aufrufe von Tabellenvariablen mit hoher Häufigkeit viel schneller als Aufrufe von temporären Tabellen mit hoher Häufigkeit.

Obwohl es verlockend ist, Kompilierungen, Neukompilierungen, automatische Statistiken, Latches, Spinlocks, Zwischenspeicherung oder andere Probleme zu beschuldigen, liegt das Problem eindeutig immer noch in der Verwaltung der temporären Tabellenbereinigung.

In SQL Server 2019 ist dies ein genauerer Aufruf, da speicherinterne Systemtabellen aktiviert sind. Bei hoher Aufrufhäufigkeit sind Tabellenvariablen jedoch immer noch leistungsfähiger.

Natürlich, so sinnierte ein Weisen, "benutze Tabellenvariablen, wenn die Wahl des Plans kein Problem darstellt".

Erik Darling
quelle
Schön - Entschuldigung, ich habe versäumt, dass Sie eine Antwort dazu hinzugefügt haben, bis Sie einfach dem Link in Ihrem "Debugging" -Blogpost folgen
Martin Smith