Database
 sql >> Datenbank >  >> RDS >> Database

Neues Trace-Flag, um die Leistung von Tabellenvariablen zu korrigieren

Es ist seit langem bekannt, dass Tabellenvariablen mit einer großen Anzahl von Zeilen problematisch sein können, da sie vom Optimierer immer als eine Zeile angesehen werden. Ohne eine Neukompilierung, nachdem die Tabellenvariable gefüllt wurde (da sie vorher leer ist), gibt es keine Kardinalität für die Tabelle, und automatische Neukompilierungen finden nicht statt, da Tabellenvariablen nicht einmal einem Schwellenwert für die Neukompilierung unterliegen. Pläne basieren daher auf einer Tabellenkardinalität von null, nicht von eins, aber das Minimum wird auf eins erhöht, wie Paul White (@SQL_Kiwi) in dieser dba.stackexchange-Antwort beschreibt.

Die Art und Weise, wie wir dieses Problem normalerweise umgehen, besteht darin, OPTION (RECOMPILE) hinzuzufügen an die Abfrage, die auf die Tabellenvariable verweist, wodurch der Optimierer gezwungen wird, die Kardinalität der Tabellenvariablen zu überprüfen, nachdem sie gefüllt wurde. Um zu vermeiden, dass jede Abfrage manuell geändert werden muss, um einen expliziten Neukompilierungshinweis hinzuzufügen, wurde ein neues Ablaufverfolgungsflag (2453) in SQL Server 2012 Service Pack 2 und im kumulativen Update Nr. 3 für SQL Server 2014 eingeführt:

    KB #2952444 :FIX:Schlechte Leistung bei Verwendung von Tabellenvariablen in SQL Server 2012 oder SQL Server 2014

Wenn das Ablaufverfolgungsflag 2453 aktiv ist, kann der Optimierer ein genaues Bild der Tabellenkardinalität erhalten, nachdem die Tabellenvariable erstellt wurde. Dies kann für viele Abfragen A Good Thing™ sein, aber wahrscheinlich nicht für alle, und Sie sollten sich darüber im Klaren sein, wie es sich von OPTION (RECOMPILE) unterscheidet . Am bemerkenswertesten ist, dass die Optimierung der Parametereinbettung, über die Paul White in diesem Beitrag spricht, unter OPTION (RECOMPILE) erfolgt , aber nicht unter diesem neuen Trace-Flag.

Ein einfacher Test

Mein erster Test bestand darin, einfach eine Tabellenvariable zu füllen und daraus auszuwählen; dies ergab die allzu vertraute geschätzte Zeilenanzahl von 1. Hier ist der Test, den ich durchgeführt habe (und ich habe den Recompile-Hinweis zum Vergleich hinzugefügt):

DBCC TRACEON(2453);
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.id, t.name
  FROM @t AS t;
 
SELECT t.id, t.name
  FROM @t AS t OPTION (RECOMPILE);
 
DBCC TRACEOFF(2453);

Wenn wir den SQL Sentry Plan Explorer verwenden, können wir sehen, dass der grafische Plan für beide Abfragen in diesem Fall identisch ist, wahrscheinlich zumindest teilweise, weil dies im wahrsten Sinne des Wortes ein trivialer Plan ist:


Grafischer Plan für einen einfachen Index-Scan gegen @t

Die Schätzungen stimmen jedoch nicht überein. Obwohl das Trace-Flag aktiviert ist, erhalten wir immer noch eine Schätzung von 1 aus dem Index-Scan, wenn wir den Recompile-Hinweis nicht verwenden:


Vergleich von Schätzungen für einen trivialen Plan im Anweisungsraster


Vergleich von Schätzungen zwischen Trace-Flag (links) und Neukompilierung (rechts)

Wenn Sie jemals persönlich in meiner Nähe waren, können Sie sich wahrscheinlich das Gesicht vorstellen, das ich an diesem Punkt gemacht habe. Ich war mir sicher, dass entweder der KB-Artikel die falsche Trace-Flag-Nummer auflistete oder dass ich eine andere aktivierte Einstellung brauchte, damit es wirklich aktiv ist.

Benjamin Nevarez (@BenjaminNevarez) wies mich schnell darauf hin, dass ich mir den KB-Artikel „In SQL Server 2012 Service Pack 2 behobene Fehler“ genauer ansehen sollte. Während sie den Text hinter einem versteckten Aufzählungszeichen unter Highlights> Relational Engine verdeckt haben, beschreibt der Artikel mit der Fixliste das Verhalten des Trace-Flags etwas besser als der ursprüngliche Artikel (Hervorhebung von mir):

Wenn eine Tabellenvariable mit anderen Tabellen verknüpft wird In SQL Server kann dies aufgrund einer ineffizienten Abfrageplanauswahl zu einer langsamen Leistung führen, da SQL Server beim Kompilieren eines Abfrageplans keine Statistiken unterstützt oder die Anzahl der Zeilen in einer Tabellenvariablen verfolgt.

Aus dieser Beschreibung geht also hervor, dass das Ablaufverfolgungsflag nur das Problem beheben soll, wenn die Tabellenvariable an einem Join teilnimmt. (Warum diese Unterscheidung im Originalartikel nicht gemacht wird, weiß ich nicht.) Aber es funktioniert auch, wenn wir die Abfragen etwas mehr Arbeit machen lassen – die obige Abfrage wird vom Optimierer als trivial angesehen, und das Trace-Flag nicht. Versuchen Sie nicht einmal, in diesem Fall etwas zu tun. Aber es wird greifen, wenn eine kostenbasierte Optimierung durchgeführt wird, auch ohne Join; Das Trace-Flag hat einfach keine Auswirkungen auf triviale Pläne. Hier ist ein Beispiel für einen nicht-trivialen Plan, der keinen Join beinhaltet:

DBCC TRACEON(2453);
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT TOP (100) t.id, t.name
  FROM @t AS t ORDER BY NEWID();
 
SELECT TOP (100) t.id, t.name
  FROM @t AS t ORDER BY NEWID() OPTION (RECOMPILE);
 
DBCC TRACEOFF(2453);

Dieser Plan ist nicht mehr trivial; Optimierung wird als vollständig markiert. Der Großteil der Kosten wird zu einem Sortieroperator verschoben:


Weniger trivialer grafischer Plan

Und die Schätzungen stimmen für beide Abfragen überein (dieses Mal erspare ich Ihnen die Tooltipps, aber ich kann Ihnen versichern, dass sie gleich sind):


Anweisungsraster für weniger triviale Pläne mit und ohne Neukompilierungshinweis

Es scheint also, dass der KB-Artikel nicht ganz genau ist – ich konnte das erwartete Verhalten des Trace-Flags erzwingen, ohne einen Join einzuführen. Aber ich möchte es auch mit einem Join testen.

Ein besserer Test

Nehmen wir dieses einfache Beispiel mit und ohne Trace-Flag:

--DBCC TRACEON(2453);
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.name, c.name
  FROM @t AS t
  LEFT OUTER JOIN sys.all_columns AS c
  ON t.id = c.[object_id];
 
--DBCC TRACEOFF(2453);

Ohne das Ablaufverfolgungsflag schätzt der Optimierer, dass eine Zeile aus dem Index-Scan gegen die Tabellenvariable stammt. Bei aktiviertem Trace-Flag werden jedoch die 1.000 Zeilen angezeigt:


Vergleich von Index-Scan-Schätzungen (kein Trace-Flag auf der linken Seite, Trace-Flag rechts)

Die Unterschiede hören hier nicht auf. Wenn wir genauer hinschauen, können wir eine Vielzahl unterschiedlicher Entscheidungen erkennen, die der Optimierer getroffen hat, die alle auf diesen besseren Schätzungen beruhen:


Vergleich von Plänen (kein Trace-Flag links, Trace-Flag rechts)

Eine kurze Zusammenfassung der Unterschiede:

  • Die Abfrage ohne Trace-Flag hat 4.140 Lesevorgänge ausgeführt, während die Abfrage mit der verbesserten Schätzung nur 424 ausgeführt hat (ungefähr eine Reduzierung um 90 %).
  • Der Optimierer schätzte, dass die gesamte Abfrage 10 Zeilen ohne das Trace-Flag zurückgeben würde, und viel genauere 2.318 Zeilen, wenn das Trace-Flag verwendet würde.
  • Ohne das Trace-Flag hat sich der Optimierer dafür entschieden, einen Join mit verschachtelten Schleifen durchzuführen (was sinnvoll ist, wenn eine der Eingaben als sehr klein eingeschätzt wird). Dies führte dazu, dass der Verkettungsoperator und beide Indexsuchen 1.000 Mal ausgeführt wurden, im Gegensatz zu dem unter dem Trace-Flag ausgewählten Hash-Match, bei dem der Verkettungsoperator und beide Scans nur einmal ausgeführt wurden.
  • Die Registerkarte Tabellen-I/O zeigt auch 1.000 Scans (Bereichsscans getarnt als Indexsuchen) und eine viel höhere Anzahl logischer Lesevorgänge gegenüber syscolpars (die Systemtabelle hinter sys.all_columns ).
  • Obwohl die Dauer nicht wesentlich beeinflusst wurde (24 Millisekunden gegenüber 18 Millisekunden), können Sie sich wahrscheinlich vorstellen, welche Auswirkungen diese anderen Unterschiede auf eine ernsthaftere Suchanfrage haben könnten.
  • Schalten wir das Diagramm auf geschätzte Kosten um, sehen wir, wie sehr die Tabellenvariable den Optimierer ohne Trace-Flag täuschen kann:


Vergleich der geschätzten Zeilenanzahl (kein Trace-Flag auf der linken Seite, trace Flagge rechts)

Es ist klar und nicht überraschend, dass der Optimierer bei der Auswahl des richtigen Plans bessere Arbeit leistet, wenn er einen genauen Überblick über die beteiligte Kardinalität hat. Aber um welchen Preis?

Neukompilierung und Overhead

Wenn wir OPTION (RECOMPILE) verwenden Mit dem obigen Batch erhalten wir ohne aktiviertes Trace-Flag den folgenden Plan – der ziemlich identisch mit dem Plan mit dem Trace-Flag ist (der einzige erkennbare Unterschied besteht darin, dass die geschätzten Zeilen 2.316 statt 2.318 sind):


Gleiche Abfrage mit OPTION (RECOMPILE)

Dies könnte Sie also zu der Annahme verleiten, dass das Trace-Flag ähnliche Ergebnisse erzielt, indem es jedes Mal eine Neukompilierung für Sie auslöst. Wir können dies mit einer sehr einfachen Sitzung für erweiterte Ereignisse untersuchen:

CREATE EVENT SESSION [CaptureRecompiles] ON SERVER 
ADD EVENT sqlserver.sql_statement_recompile
  (
    ACTION(sqlserver.sql_text)
  ) 
  ADD TARGET package0.asynchronous_file_target
  (
    SET FILENAME = N'C:\temp\CaptureRecompiles.xel'
  );
GO
ALTER EVENT SESSION [CaptureRecompiles] ON SERVER STATE = START;

Ich habe die folgenden Batches ausgeführt, die 20 Abfragen mit (a) keiner Recompile-Option oder Trace-Flag, (b) der Recompile-Option und (c) einem Trace-Flag auf Sitzungsebene ausgeführt haben.

/* default - no trace flag, no recompile */
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.name, c.name
  FROM @t AS t
  LEFT OUTER JOIN sys.all_columns AS c
  ON t.id = c.[object_id];
 
GO 20
 
/* recompile */
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.name, c.name
  FROM @t AS t
  LEFT OUTER JOIN sys.all_columns AS c
  ON t.id = c.[object_id] OPTION (RECOMPILE);
 
GO 20
 
/* trace flag */
 
DBCC TRACEON(2453);  
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.name, c.name
  FROM @t AS t
  LEFT OUTER JOIN sys.all_columns AS c
  ON t.id = c.[object_id];
 
DBCC TRACEOFF(2453);
 
GO 20

Dann habe ich mir die Ereignisdaten angesehen:

SELECT 
  sql_text = LEFT(sql_text, 255),
  recompile_count = COUNT(*)
FROM 
(
  SELECT 
    x.x.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)')
  FROM 
    sys.fn_xe_file_target_read_file(N'C:\temp\CaptureRecompiles*.xel',NULL,NULL,NULL) AS f
    CROSS APPLY (SELECT CONVERT(XML, f.event_data)) AS x(x)
) AS x(sql_text)
GROUP BY LEFT(sql_text, 255);

Die Ergebnisse zeigen, dass unter der Standardabfrage keine Neukompilierungen erfolgten, die Anweisung, die auf die Tabellenvariable verweist, wurde einmal neu kompiliert unter dem Trace-Flag und, wie Sie vielleicht erwarten, jedes Mal mit dem RECOMPILE Möglichkeit:

sql_text recompile_count
/* recompile */ DECLARE @t TABLE (i INT … 20
/* Trace-Flag */ DBCC TRACEON(2453); ERKLÄRE @t … 1

Ergebnisse der Abfrage von XEvents-Daten

Als Nächstes habe ich die Sitzung „Erweiterte Ereignisse“ deaktiviert und dann den Stapel so geändert, dass er maßstabsgetreu gemessen werden kann. Im Wesentlichen misst der Code 1.000 Iterationen zum Erstellen und Füllen einer Tabellenvariablen und wählt dann ihre Ergebnisse in einer #temp-Tabelle aus (eine Möglichkeit, die Ausgabe so vieler Wegwerf-Resultsets zu unterdrücken), wobei jede der drei Methoden verwendet wird.

SET NOCOUNT ON;
 
/* default - no trace flag, no recompile */
 
SELECT SYSDATETIME();
GO
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.id, c.name
  INTO #x
  FROM @t AS t
  LEFT OUTER JOIN sys.all_columns AS c
  ON t.id = c.[object_id];
 
DROP TABLE #x;
 
GO 1000
SELECT SYSDATETIME();
GO
 
 
/* recompile */
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.id, c.name
  INTO #x
  FROM @t AS t
  LEFT OUTER JOIN sys.all_columns AS c
  ON t.id = c.[object_id] OPTION (RECOMPILE);
 
DROP TABLE #x;
 
GO 1000
SELECT SYSDATETIME();
GO
 
 
/* trace flag */
 
DBCC TRACEON(2453);  
 
DECLARE @t TABLE(id INT PRIMARY KEY, name SYSNAME NOT NULL UNIQUE);
 
INSERT @t SELECT TOP (1000) [object_id], name FROM sys.all_objects;
 
SELECT t.id, c.name
  INTO #x
  FROM @t AS t
  LEFT OUTER JOIN sys.all_columns AS c
  ON t.id = c.[object_id];
 
DROP TABLE #x;
 
DBCC TRACEOFF(2453);  
 
GO 1000
SELECT SYSDATETIME();
GO

Ich habe diese Charge 10 Mal laufen lassen und die Durchschnittswerte genommen; sie waren:

Methode Durchschnittliche Dauer
(Millisekunden)
Standard 23.148,4
Neu kompilieren 29.959,3
Trace-Flag 22.100,7

Durchschnittliche Dauer für 1.000 Iterationen

In diesem Fall war das Abrufen der richtigen Schätzungen jedes Mal, wenn der Neukompilierungshinweis verwendet wurde, viel langsamer als das Standardverhalten, aber die Verwendung des Ablaufverfolgungsflags war etwas schneller. Dies ist sinnvoll, weil – während beide Methoden das Standardverhalten der Verwendung einer falschen Schätzung korrigieren (und als Ergebnis einen schlechten Plan erhalten), Rekompilierungen Ressourcen verbrauchen und, wenn sie keinen effizienteren Plan liefern oder liefern können, dazu neigen tragen zur gesamten Chargendauer bei.

Scheint einfach, aber warte…

Der obige Test ist leicht – und absichtlich – fehlerhaft. Wir fügen jedes Mal dieselbe Anzahl von Zeilen (1.000) in die Tabellenvariable ein . Was passiert, wenn die anfängliche Befüllung der Tabellenvariable für verschiedene Chargen variiert? Sicherlich werden wir dann Neukompilierungen sehen, sogar unter dem Trace-Flag, oder? Zeit für einen weiteren Test. Lassen Sie uns eine etwas andere Sitzung für erweiterte Ereignisse einrichten, nur mit einem anderen Zieldateinamen (um keine Daten aus der anderen Sitzung zu verwechseln):

CREATE EVENT SESSION [CaptureRecompiles_v2] ON SERVER 
ADD EVENT sqlserver.sql_statement_recompile
  (
    ACTION(sqlserver.sql_text)
  ) 
  ADD TARGET package0.asynchronous_file_target
  (
    SET FILENAME = N'C:\temp\CaptureRecompiles_v2.xel'
  );
GO
ALTER EVENT SESSION [CaptureRecompiles_v2] ON SERVER STATE = START;

Sehen wir uns nun diesen Batch an und richten für jede Iteration deutlich unterschiedliche Zeilenzahlen ein. Wir führen dies dreimal aus und entfernen die entsprechenden Kommentare, sodass wir einen Batch ohne Trace-Flag oder explizite Neukompilierung, einen Batch mit dem Trace-Flag und einen Batch mit OPTION (RECOMPILE) haben (Durch einen genauen Kommentar am Anfang sind diese Stapel an Stellen wie der Ausgabe von erweiterten Ereignissen leichter zu identifizieren):

/* default, no trace flag or recompile */
/* recompile */
/* trace flag */
 
DECLARE @i INT = 1;
 
WHILE @i <= 6
BEGIN
  --DBCC TRACEON(2453); -- uncomment this for trace flag
 
  DECLARE @t TABLE(id INT PRIMARY KEY);
 
  INSERT @t SELECT TOP (CASE @i 
      WHEN 1 THEN 24
	  WHEN 2 THEN 1782
	  WHEN 3 THEN 1701
	  WHEN 4 THEN 12
	  WHEN 5 THEN 15
	  WHEN 6 THEN 1560 
	END) [object_id]
    FROM sys.all_objects;
 
  SELECT t.id, c.name
    FROM @t AS t
    INNER JOIN sys.all_objects AS c
    ON t.id = c.[object_id]
    --OPTION (RECOMPILE); -- uncomment this for recompile
 
  --DBCC TRACEOFF(2453); -- uncomment this for trace flag
 
  DELETE @t;
  SET @i += 1;
END

Ich habe diese Batches in Management Studio ausgeführt, sie einzeln im Plan Explorer geöffnet und die Anweisungsstruktur nur nach SELECT gefiltert Anfrage. Wir können das unterschiedliche Verhalten in den drei Stapeln erkennen, indem wir uns die geschätzten und tatsächlichen Zeilen ansehen:


Vergleich von drei Batches, wobei geschätzte und tatsächliche Zeilen betrachtet werden
Im Raster ganz rechts können Sie deutlich sehen, wo unter dem Ablaufverfolgungs-Flag keine Neukompilierung stattgefunden hat

Wir können die XEvents-Daten überprüfen, um zu sehen, was bei Neukompilierungen tatsächlich passiert ist:

SELECT 
  sql_text = LEFT(sql_text, 255),
  recompile_count = COUNT(*)
FROM 
(
  SELECT 
    x.x.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)')
  FROM 
    sys.fn_xe_file_target_read_file(N'C:\temp\CaptureRecompiles_v2*.xel',NULL,NULL,NULL) AS f
    CROSS APPLY (SELECT CONVERT(XML, f.event_data)) AS x(x)
) AS x(sql_text)
GROUP BY LEFT(sql_text, 255);

Ergebnisse:

sql_text recompile_count
/* neu kompilieren */ DECLARE @i INT =1; WÄHREND … 6
/* Trace-Flag */ DECLARE @i INT =1; WÄHREND … 4

Ergebnisse der Abfrage von XEvents-Daten

Sehr interessant! Unter dem Trace-Flag sehen wir Neukompilierungen, aber nur, wenn der Wert des Laufzeitparameters erheblich vom zwischengespeicherten Wert abweicht. Wenn der Laufzeitwert unterschiedlich ist, aber nicht sehr viel, erhalten wir keine Neukompilierung, und es werden dieselben Schätzungen verwendet. Es ist also klar, dass das Trace-Flag einen Recompile-Schwellenwert für Tabellenvariablen einführt, und ich habe (durch einen separaten Test) bestätigt, dass dies denselben Algorithmus verwendet, der für #temp-Tabellen in diesem "alten", aber immer noch relevanten Artikel beschrieben wurde. Ich werde dies in einem Folgebeitrag beweisen.

Erneut testen wir die Leistung, indem wir den Stapel 1.000 Mal ausführen (bei deaktivierter Sitzung für erweiterte Ereignisse) und die Dauer messen:

Methode Durchschnittliche Dauer
(Millisekunden)
Standard 101.285,4
Neu kompilieren 111.423,3
Trace-Flag 110.318,2

Durchschnittliche Dauer für 1.000 Iterationen

In diesem speziellen Szenario verlieren wir etwa 10 % der Leistung, indem wir jedes Mal eine Neukompilierung erzwingen oder ein Trace-Flag verwenden. Nicht genau sicher, wie sich das Delta verteilte:Basierten die Planungen nicht signifikant auf besseren Schätzungen besser? Haben Neukompilierungen Leistungssteigerungen um so viel ausgeglichen ? Ich möchte nicht zu viel Zeit darauf verwenden, und es war ein triviales Beispiel, aber es zeigt Ihnen, dass das Spielen mit der Funktionsweise des Optimierers eine unvorhersehbare Angelegenheit sein kann. Manchmal sind Sie mit dem Standardverhalten Kardinalität =1 besser dran, da Sie wissen, dass Sie niemals unnötige Neukompilierungen verursachen werden. Das Trace-Flag kann sehr sinnvoll sein, wenn Sie Abfragen haben, bei denen Sie Tabellenvariablen wiederholt mit demselben Datensatz füllen (z. B. eine Nachschlagetabelle für Postleitzahlen) oder wenn Sie immer 50 oder 1.000 Zeilen verwenden (z eine Tabellenvariable zur Verwendung bei der Paginierung). In jedem Fall sollten Sie auf jeden Fall testen, welche Auswirkungen dies auf alle Arbeitslasten hat, bei denen Sie das Trace-Flag oder explizite Neukompilierungen einführen möchten.

TVPs und Tabellentypen

Ich war auch neugierig, wie sich dies auf Tabellentypen auswirken würde und ob wir Verbesserungen in der Kardinalität für TVPs sehen würden, wo dasselbe Symptom existiert. Also habe ich einen einfachen Tabellentyp erstellt, der die bisher verwendete Tabellenvariable nachahmt:

USE MyTestDB;
GO
 
CREATE TYPE dbo.t AS TABLE 
(
  id INT PRIMARY KEY
);

Dann habe ich den obigen Stapel genommen und einfach DECLARE @t TABLE(id INT PRIMARY KEY); ersetzt mit DECLARE @t dbo.t; – alles andere blieb genau gleich. Ich habe die gleichen drei Chargen ausgeführt und Folgendes gesehen:


Vergleich von Schätzungen und tatsächlichen Werten zwischen Standardverhalten, Option Neukompilierung und Trace-Flag 2453

Also ja, es scheint, dass das Trace-Flag bei TVPs genauso funktioniert – Neukompilierungen generieren neue Schätzungen für den Optimierer, wenn die Zeilenanzahl den Neukompilierungsschwellenwert überschreitet, und werden übersprungen, wenn die Zeilenanzahl „nah genug“ ist.

Vorteile, Nachteile und Vorbehalte

Ein Vorteil des Trace-Flags besteht darin, dass Sie einige vermeiden können neu kompilieren und trotzdem die Tabellenkardinalität sehen – solange Sie erwarten, dass die Anzahl der Zeilen in der Tabellenvariable stabil ist, oder keine signifikanten Planabweichungen aufgrund unterschiedlicher Kardinalitäten beobachten. Ein weiterer Grund ist, dass Sie es global oder auf Sitzungsebene aktivieren können und nicht alle Ihre Abfragen neu kompilieren müssen. Und schließlich, zumindest in dem Fall, in dem die Kardinalität der Tabellenvariablen stabil war, führten korrekte Schätzungen zu einer besseren Leistung als die Standardeinstellung und auch zu einer besseren Leistung als die Verwendung der Neukompilierungsoption – all diese Kompilierungen können sich sicherlich summieren.

Natürlich gibt es auch einige Nachteile. Eine, die ich oben erwähnt habe, ist die im Vergleich zu OPTION (RECOMPILE) Sie verpassen bestimmte Optimierungen, wie z. B. das Einbetten von Parametern. Ein weiterer Grund ist, dass das Ablaufverfolgungsflag bei trivialen Plänen nicht die Auswirkungen haben wird, die Sie erwarten. Und eine, die ich dabei entdeckt habe, ist die Verwendung von QUERYTRACEON Hinweis zum Erzwingen des Trace-Flags auf Abfrageebene funktioniert nicht – soweit ich das beurteilen kann, muss das Trace-Flag vorhanden sein, wenn die Tabellenvariable oder TVP erstellt und/oder gefüllt wird, damit der Optimierer die Kardinalität oben sehen kann 1.

Denken Sie daran, dass die globale Ausführung des Ablaufverfolgungsflags die Möglichkeit von Abfrageplanregressionen für jede Abfrage einführt, die eine Tabellenvariable enthält (weshalb dieses Feature überhaupt unter einem Ablaufverfolgungsflag eingeführt wurde). Stellen Sie daher sicher, dass Sie Ihre gesamte Arbeitslast testen egal wie Sie das Ablaufverfolgungsflag verwenden. Wenn Sie dieses Verhalten testen, tun Sie dies bitte auch in einer Benutzerdatenbank; Einige der Optimierungen und Vereinfachungen, die Sie normalerweise erwarten, treten einfach nicht auf, wenn der Kontext auf tempdb gesetzt ist, sodass jegliches Verhalten, das Sie dort beobachten, möglicherweise nicht konsistent bleibt, wenn Sie den Code und die Einstellungen in eine Benutzerdatenbank verschieben.

Schlussfolgerung

Wenn Sie Tabellenvariablen oder TVPs mit einer großen, aber relativ konsistenten Anzahl von Zeilen verwenden, kann es hilfreich sein, dieses Ablaufverfolgungsflag für bestimmte Batches oder Prozeduren zu aktivieren, um eine genaue Tabellenkardinalität zu erhalten, ohne manuell eine Neukompilierung für einzelne Abfragen zu erzwingen. Sie können das Ablaufverfolgungsflag auch auf Instanzebene verwenden, was sich auf alle Abfragen auswirkt. Aber wie bei jeder Änderung müssen Sie in beiden Fällen sorgfältig die Leistung Ihrer gesamten Arbeitslast testen, explizit nach Regressionen Ausschau halten und sicherstellen, dass Sie das Verhalten des Ablaufverfolgungsflags wollen, da Sie der Stabilität Ihrer Tabellenvariablen vertrauen können Zeilenanzahl.

Ich freue mich, dass das Trace-Flag zu SQL Server 2014 hinzugefügt wurde, aber es wäre besser, wenn dies einfach zum Standardverhalten würde. Nicht, dass die Verwendung großer Tabellenvariablen gegenüber großen #temp-Tabellen einen signifikanten Vorteil hätte, aber es wäre schön, mehr Parität zwischen diesen beiden temporären Strukturtypen zu sehen, die auf einer höheren Ebene diktiert werden könnten. Je mehr Parität wir haben, desto weniger Menschen müssen darüber nachdenken, welche sie verwenden sollen (oder haben zumindest weniger Kriterien, die sie bei der Auswahl berücksichtigen müssen). Martin Smith hat auf dba.stackexchange ein großartiges Q&A, das wahrscheinlich jetzt aktualisiert werden muss:Was ist der Unterschied zwischen einer temporären Tabelle und einer Tabellenvariablen in SQL Server?

Wichtiger Hinweis

Wenn Sie SQL Server 2012 Service Pack 2 installieren (unabhängig davon, ob dieses Trace-Flag verwendet werden soll oder nicht), lesen Sie bitte auch meinen Beitrag über eine Regression in SQL Server 2012 und 2014, die – in seltenen Fällen – eintreten kann potenzieller Datenverlust oder -beschädigung während der Online-Indexneuerstellung. Es sind kumulative Updates für SQL Server 2012 SP1 und SP2 sowie für SQL Server 2014 verfügbar. Für den RTM-Zweig 2012 wird es keine Fehlerbehebung geben.

Weitere Tests

Ich habe noch andere Dinge auf meiner Liste zum Testen. Zum einen möchte ich sehen, ob dieses Ablaufverfolgungsflag Auswirkungen auf In-Memory-Tabellentypen in SQL Server 2014 hat. Ich werde auch zweifelsfrei beweisen, dass das Ablaufverfolgungsflag 2453 denselben Neukompilierungsschwellenwert für die Tabelle verwendet Variablen und TVPs wie bei #temp-Tabellen.