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:StmtRecompile
Ereignisse in Profiler (Abbildung unten) zeigt , dass diese Ereignisse nur einmal vorkommen (auf dem ersten Aufruf der #temp
Tabelle gespeicherten Prozedur) und die anderen 9.999 Hinrichtungen nicht eines dieser Ereignisse erhöhen. (Die Tabellenvariablenversion erhält keines dieser Ereignisse.)
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
quelle
#temp
Tabelle nur einmal erstellt werden, obwohl sie anschließend 9.999 Mal gelöscht und neu aufgefüllt wurden.Antworten:
Die Ausgabe von
SET STATISTICS IO ON
für beide sieht ähnlich ausGibt
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.NUM
die#temp
Tabelle 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 von
EXEC dbo.T2 10
und diese Ergebnisse für 10.000 Hinrichtungen von
EXEC dbo.V2 10
Es ist also klar, dass die Anzahl der
PAGELATCH_SH
Wartezeiten in der#temp
Tabelle viel höher ist. Mir ist keine Möglichkeit bekannt, die Wait-Ressource zum erweiterten Ereignistrace hinzuzufügen, um dies weiter zu untersuchenWährend eines anderen Verbindungsabrufs
sys.dm_os_waiting_tasks
Nachdem es etwa 15 Sekunden lang laufen gelassen worden war, hatte es die folgenden Ergebnisse gesammelt
Beide Seiten, die zwischengespeichert werden, gehören zu (verschiedenen) nicht gruppierten Indizes für die Basistabelle mit den
tempdb.sys.sysschobjs
Namen'nc1'
und'nc2'
.Das Abfragen
tempdb.sys.fn_dblog
wä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#temp
Version erforderlichen .Wenn Sie die Transaktionsprotokolleinträge für die
#temp
Tabellenversion des SP genauer betrachten, werden bei jedem nachfolgenden Aufruf der gespeicherten Prozedur drei Transaktionen und bei der Tabellenvariablen nur zwei Transaktionen erstellt.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ägeLOP_BEGIN_XACT
/LOP_COMMIT_XACT
.Die
CREATE TABLE
Transaktion erscheint nur in der#Temp
Version und sieht wie folgt aus.Die
FCheckAndCleanupCachedTempTable
Transaktion erscheint in beiden, hat aber 6 zusätzliche Einträge in der#temp
Version. Dies sind die 6 Zeilen, auf die Bezug genommen wird,sys.sysschobjs
und sie haben genau das gleiche Muster wie oben.Betrachtet man diese 6 Zeilen in beiden Transaktionen, so entsprechen sie denselben Operationen. Das erste
LOP_MODIFY_ROW, LCX_CLUSTERED
ist eine Aktualisierung dermodify_date
Spalte insys.objects
. Die verbleibenden fünf Zeilen befassen sich alle mit dem Umbenennen von Objekten. Daname
es sich um eine Schlüsselspalte der beiden betroffenen NCIs (nc1
undnc2
) 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
#temp
Tabellenversion, wenn die gespeicherte Prozedur einen Teil der von derFCheckAndCleanupCachedTempTable
Transaktion ausgeführten Bereinigung beendet , die temporäre Tabelle von so etwas wie#T__________________________________________________________________________________________________________________00000000E316
einem anderen internen Namen umbenannt wird,#2F4A0079
und wenn sie eingegeben wirdCREATE TABLE
, benennt die Transaktion sie zurück. Dieser Flip-Flop-Name kann von einer Verbindung gesehen werden, diedbo.T2
in einer Schleife ausgeführt wird, während sie in einer anderen ausgeführt wirdBeispiel Ergebnisse
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
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
Sowohl unter der Funktion create als auch unter der Funktion release wird die Funktion
CMEDProxyObject::SetName
mit dem Beispielwert inclusive von angezeigt19.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
Temporäres Tabellenprofil
Tabellenvariablenprofil
quelle
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:
Temp-Tabelle:
Tabellenvariable:
Um zu verhindern, dass ASYNC_NETWORK_IO möglicherweise wartet , verwende ich Wrapper-Prozeduren.
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:
Immer noch ein sehr deutlicher Unterschied, was? Aber was trifft SQL Server jetzt?
Mit Blick auf die oberen zwei Erhöhungen der diffed Proben, wir sehen
sqlmin
undsqlsqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucket
sind die beiden größten Straftäter.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.
Das Durchsuchen der Call-Stacks für den Tabellenvariablentest zeigt keinen der Haupttäter:
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:
Zeitunterschied:
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>::GetNextUserDataInHashBucket
ist es aber .SQL Server 2019 (In-Memory-Tempdb-Systemtabellen)
Was ist mit dieser neuen Tabelle für Speichersysteme? Hm? Sup damit?
Lass es uns einschalten!
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:
Zeitunterschied:
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:
Ein interessanter Punkt im Diff sind die Aufrufe von
hkengine!
, was offensichtlich erscheinen mag, da jetzt hekatonische Funktionen verwendet werden.Was die ersten beiden Punkte im Diff angeht, kann ich nicht viel daraus machen
ntoskrnl!?
:Oder
sqltses!CSqlSortManager_80::GetSortKey
, aber sie sind hier, damit Smrtr Ppl ™ sich ansieht: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".
quelle