Parameter-Sniffing
Die Abfrageparametrisierung fördert die Wiederverwendung von zwischengespeicherten Ausführungsplänen, wodurch unnötige Kompilierungen vermieden und die Anzahl von Ad-hoc-Abfragen im Plancache reduziert werden.
Das sind alles gute Dinge, vorausgesetzt Die parametrisierte Abfrage sollte wirklich denselben zwischengespeicherten Ausführungsplan für verschiedene Parameterwerte verwenden. Ein Ausführungsplan, der für einen Parameterwert effizient ist, kann es nicht sein eine gute Wahl für andere mögliche Parameterwerte sein.
Wenn Parameter Sniffing aktiviert ist (Standardeinstellung), wählt SQL Server einen Ausführungsplan basierend auf den bestimmten Parameterwerten aus, die zum Zeitpunkt der Kompilierung vorhanden sind. Die implizite Annahme ist, dass parametrisierte Anweisungen am häufigsten mit den häufigsten Parameterwerten ausgeführt werden. Das klingt vernünftig (sogar offensichtlich) und funktioniert tatsächlich oft gut.
Ein Problem kann auftreten, wenn eine automatische Neukompilierung des zwischengespeicherten Plans erfolgt. Eine Neukompilierung kann aus allen möglichen Gründen ausgelöst werden, beispielsweise weil ein Index, der vom zwischengespeicherten Plan verwendet wird, gelöscht wurde (eine Korrektheit Neukompilierung) oder weil sich statistische Informationen geändert haben (eine Optimalität neu kompilieren).
Was auch immer die genaue Ursache ist Bei der Neukompilierung des Plans besteht die Möglichkeit, dass ein atypischer value wird zum Zeitpunkt der Generierung des neuen Plans als Parameter übergeben. Dies kann zu einem neuen zwischengespeicherten Plan führen (basierend auf dem erschnüffelten atypischen Parameterwert), der für die meisten Ausführungen, für die er wiederverwendet wird, nicht gut ist.
Es ist nicht einfach vorherzusagen, wann ein bestimmter Ausführungsplan neu kompiliert wird (z. B. weil sich Statistiken ausreichend geändert haben), was zu einer Situation führt, in der ein wiederverwendbarer Plan guter Qualität plötzlich durch einen ganz anderen Plan ersetzt werden kann, der für atypische Parameterwerte optimiert ist.
Ein solches Szenario tritt auf, wenn der atypische Wert sehr selektiv ist, was zu einem Plan führt, der für eine kleine Anzahl von Zeilen optimiert ist. Solche Pläne verwenden häufig Singlethread-Ausführung, Joins mit verschachtelten Schleifen und Lookups. Ernsthafte Leistungsprobleme können auftreten, wenn dieser Plan für andere Parameterwerte wiederverwendet wird, die eine viel größere Anzahl von Zeilen generieren.
Parameter-Sniffing deaktivieren
Das Parameter-Sniffing kann mit dem dokumentierten Trace-Flag 4136 deaktiviert werden. Das Trace-Flag wird auch für pro Abfrage unterstützt Nutzung über das QUERYTRACEON
Abfragehinweis. Beide gelten ab SQL Server 2005 Service Pack 4 (und etwas früher, wenn Sie kumulative Updates auf Service Pack 3 anwenden).
Ab SQL Server 2016 kann das Parameter-Sniffing auch auf Datenbankebene deaktiviert werden , mit dem PARAMETER_SNIFFING
Argument für ALTER DATABASE SCOPED CONFIGURATION
.
Wenn das Parameter-Sniffing deaktiviert ist, verwendet SQL Server die durchschnittliche Verteilung Statistiken zur Auswahl eines Ausführungsplans.
Dies klingt auch nach einem vernünftigen Ansatz (und kann dazu beitragen, die Situation zu vermeiden, in der der Plan für einen ungewöhnlich selektiven Parameterwert optimiert wird), aber es ist auch keine perfekte Strategie:Ein Plan, der für einen „durchschnittlichen“ Wert optimiert ist, könnte durchaus sein ernsthaft suboptimal für die häufig gesehenen Parameterwerte.
Stellen Sie sich einen Ausführungsplan vor, der speicherintensive Operatoren wie Sortierungen und Hashes enthält. Da Speicher reserviert wird, bevor die Abfrageausführung beginnt, kann ein parametrisierter Plan, der auf durchschnittlichen Verteilungswerten basiert, in tempdb überlaufen für allgemeine Parameterwerte, die mehr Daten erzeugen als vom Optimierer erwartet.
Speicherreservierungen können normalerweise während der Abfrageausführung nicht wachsen, unabhängig davon, wie viel freier Speicher der Server möglicherweise hat. Bestimmte Anwendungen profitieren vom Deaktivieren des Parameter-Sniffing (ein Beispiel finden Sie in diesem Archivbeitrag des Dynamics AX Performance-Teams).
Für die meisten Workloads ist die vollständige Deaktivierung des Parameter-Sniffing die falsche Lösung , und kann sogar eine Katastrophe sein. Parameter Sniffing ist eine heuristische Optimierung:Es funktioniert auf den meisten Systemen meistens besser als die Verwendung von Durchschnittswerten.
Suchhinweise
SQL Server stellt eine Reihe von Abfragehinweisen und anderen Optionen bereit, um das Verhalten des Parameter-Sniffing zu optimieren:
- Der
OPTIMIZE FOR (@parameter = value)
Abfragehinweis erstellt einen wiederverwendbaren Plan basierend auf einem bestimmten Wert. OPTIMIZE FOR (@parameter UNKNOWN)
verwendet durchschnittliche Verteilungsstatistiken für einen bestimmten Parameter.OPTIMIZE FOR UNKNOWN
verwendet die durchschnittliche Verteilung für alle Parameter (gleiche Wirkung wie Trace-Flag 4136).- Der
WITH RECOMPILE
Stored Procedure Option kompiliert für jede Ausführung einen neuen Prozedurplan. - Die
OPTION (RECOMPILE)
Abfragehinweis erstellt einen neuen Plan für eine einzelne Anweisung.
Die alte Technik des „Parameter Hiding“ (Prozedurparameter lokalen Variablen zuweisen und stattdessen auf die Variablen verweisen) hat denselben Effekt wie die Angabe von OPTIMIZE FOR UNKNOWN
. Es kann auf Instanzen vor SQL Server 2008 nützlich sein (das OPTIMIZE FOR
Hinweis war neu für 2008).
Man könnte argumentieren, dass jeder parametrisierte Anweisung sollte auf Sensibilität für Parameterwerte überprüft und entweder in Ruhe gelassen werden (wenn das Standardverhalten gut funktioniert) oder explizit mit einer der obigen Optionen angedeutet werden.
Dies wird in der Praxis selten durchgeführt, zum Teil, weil die Durchführung einer umfassenden Analyse für alle möglichen Parameterwerte zeitaufwändig sein kann und ziemlich fortgeschrittene Fähigkeiten erfordert.
Meistens wird eine solche Analyse nicht durchgeführt und Parametersensitivitätsprobleme werden als und angegangen wenn sie in der Produktion auftreten.
Dieser Mangel an vorheriger Analyse ist wahrscheinlich einer der Hauptgründe dafür, dass Parameter-Sniffing einen schlechten Ruf hat. Es lohnt sich, sich des möglichen Auftretens von Problemen bewusst zu sein und zumindest eine schnelle Analyse von Anweisungen durchzuführen, die wahrscheinlich Leistungsprobleme verursachen, wenn sie mit einem atypischen Parameterwert neu kompiliert werden.
Was ist ein Parameter?
Einige würden sagen, dass ein SELECT
Anweisung, die auf eine lokale Variable verweist, ist eine „parametrisierte Anweisung“ Art, aber das ist nicht die Definition, die SQL Server verwendet.
Ein sinnvoller Hinweis darauf, dass eine Anweisung Parameter verwendet, findet sich in den Planeigenschaften (siehe Parameter Registerkarte im Sentry One Plan Explorer. Oder klicken Sie in SSMS auf den Stammknoten des Abfrageplans und öffnen Sie die Eigenschaften Fenster und erweitern Sie die Parameterliste Knoten):
Der „kompilierte Wert“ zeigt den Sniff-Wert des Parameters, der zum Kompilieren des zwischengespeicherten Plans verwendet wurde. Der „Laufzeitwert“ zeigt den Wert des Parameters für die bestimmte im Plan erfasste Ausführung an.
Jede dieser Eigenschaften kann unter verschiedenen Umständen leer sein oder fehlen. Wenn eine Abfrage nicht parametrisiert ist, fehlen einfach alle Eigenschaften.
Nur weil in SQL Server nichts jemals einfach ist, gibt es Situationen, in denen die Parameterliste gefüllt werden kann, aber die Anweisung immer noch nicht parametrisiert ist. Dies kann auftreten, wenn SQL Server eine einfache Parametrisierung versucht (später erläutert), aber entscheidet, dass der Versuch „unsicher“ ist. In diesem Fall sind Parametermarkierungen vorhanden, aber der Ausführungsplan ist tatsächlich nicht parametrisiert.
Sniffing ist nicht nur für gespeicherte Prozeduren
Parameter-Sniffing tritt auch auf, wenn ein Batch explizit zur Wiederverwendung mit sp_executesql
parametrisiert wird .
Zum Beispiel:
EXECUTE sys.sp_executesql N' SELECT P.ProductID, P.Name, TotalQty = SUM(TH.Quantity) FROM Production.Product AS P JOIN Production.TransactionHistory AS TH ON TH.ProductID = P.ProductID WHERE P.Name LIKE @NameLike GROUP BY P.ProductID, P.Name; ', N'@NameLike nvarchar(50)', @NameLike = N'K%';
Der Optimierer wählt einen Ausführungsplan basierend auf dem geschnüffelten Wert von @NameLike
Parameter. Der Parameterwert „K%“ stimmt schätzungsweise mit sehr wenigen Zeilen im Product
überein Tabelle, also wählt der Optimierer eine Nested-Loop-Join- und Schlüsselsuchstrategie:
Durch erneutes Ausführen der Anweisung mit einem Parameterwert von „[H-R]%“ (der mit viel mehr Zeilen übereinstimmt) wird der zwischengespeicherte parametrisierte Plan erneut verwendet:
EXECUTE sys.sp_executesql N' SELECT P.ProductID, P.Name, TotalQty = SUM(TH.Quantity) FROM Production.Product AS P JOIN Production.TransactionHistory AS TH ON TH.ProductID = P.ProductID WHERE P.Name LIKE @NameLike GROUP BY P.ProductID, P.Name; ', N'@NameLike nvarchar(50)', @NameLike = N'[H-R]%';
Die AdventureWorks Die Beispieldatenbank ist zu klein, um dies zu einem Leistungsdesaster zu machen, aber dieser Plan ist sicherlich nicht optimal für den zweiten Parameterwert.
Wir können den Plan sehen, den der Optimierer gewählt hätte, indem wir den Plan-Cache löschen und die zweite Abfrage erneut ausführen:
Wenn eine größere Anzahl von Übereinstimmungen erwartet wird, bestimmt der Optimierer, dass ein Hash-Join und eine Hash-Aggregation bessere Strategien sind.
T-SQL-Funktionen
Parameter-Sniffing tritt auch bei T-SQL-Funktionen auf, obwohl dies durch die Art und Weise, wie Ausführungspläne generiert werden, schwieriger zu erkennen ist.
Es gibt gute Gründe, T-SQL-Skalar- und Multi-Statement-Funktionen im Allgemeinen zu vermeiden, daher ist hier nur für Bildungszwecke eine T-SQL-Multi-Statement-Table-Value-Funktionsversion unserer Testabfrage:
CREATE FUNCTION dbo.F (@NameLike nvarchar(50)) RETURNS @Result TABLE ( ProductID integer NOT NULL PRIMARY KEY, Name nvarchar(50) NOT NULL, TotalQty integer NOT NULL ) WITH SCHEMABINDING AS BEGIN INSERT @Result SELECT P.ProductID, P.Name, TotalQty = SUM(TH.Quantity) FROM Production.Product AS P JOIN Production.TransactionHistory AS TH ON TH.ProductID = P.ProductID WHERE P.Name LIKE @NameLike GROUP BY P.ProductID, P.Name; RETURN; END;
Die folgende Abfrage verwendet die Funktion, um Informationen für Produktnamen anzuzeigen, die mit „K“ beginnen:
SELECT Result.ProductID, Result.Name, Result.TotalQty FROM dbo.F(N'K%') AS Result;
Das Erkennen von Parameter-Sniffing mit einer eingebetteten Funktion ist schwieriger, da SQL Server keinen separaten Abfrageplan nach der Ausführung (tatsächlich) für jeden Funktionsaufruf zurückgibt. Die Funktion könnte viele Male innerhalb einer einzigen Anweisung aufgerufen werden, und Benutzer wären nicht beeindruckt, wenn SSMS versuchen würde, eine Million Funktionsaufrufpläne für eine einzige Abfrage anzuzeigen.
Aufgrund dieser Entwurfsentscheidung ist der tatsächliche Plan, der von SQL Server für unsere Testabfrage zurückgegeben wird, nicht sehr hilfreich:
Dennoch gibt es Möglichkeiten, Parameter-Sniffing mit eingebetteten Funktionen in Aktion zu sehen. Die Methode, die ich hier gewählt habe, besteht darin, den Plan-Cache zu inspizieren:
SELECT DEQS.plan_generation_num, DEQS.execution_count, DEQS.last_logical_reads, DEQS.last_elapsed_time, DEQS.last_rows, DEQP.query_plan FROM sys.dm_exec_query_stats AS DEQS CROSS APPLY sys.dm_exec_sql_text(DEQS.plan_handle) AS DEST CROSS APPLY sys.dm_exec_query_plan(DEQS.plan_handle) AS DEQP WHERE DEST.objectid = OBJECT_ID(N'dbo.F', N'TF');
Dieses Ergebnis zeigt, dass der Funktionsplan einmal ausgeführt wurde, zu einem Preis von 201 logischen Lesevorgängen mit einer verstrichenen Zeit von 2891 Mikrosekunden, und die letzte Ausführung eine Zeile zurückgegeben hat. Die zurückgegebene XML-Plandarstellung zeigt, dass der Parameterwert war geschnüffelt:
Führen Sie die Anweisung nun erneut mit einem anderen Parameter aus:
SELECT Result.ProductID, Result.Name, Result.TotalQty FROM dbo.F(N'[H-R]%') AS Result;
Der Nachausführungsplan zeigt, dass 306 Zeilen von der Funktion zurückgegeben wurden:
Die Plan-Cache-Abfrage zeigt, dass der zwischengespeicherte Ausführungsplan für die Funktion wiederverwendet wurde (execution_count
=2):
Es zeigt auch eine viel höhere Anzahl von logischen Lesevorgängen und eine längere verstrichene Zeit im Vergleich zum vorherigen Lauf. Dies steht im Einklang mit der Wiederverwendung eines verschachtelten Schleifen- und Suchplans, aber um absolut sicher zu sein, kann der Funktionsplan nach der Ausführung mit Erweiterten Ereignissen erfasst werden oder den SQL Server Profiler Werkzeug:
Da das Parameter-Sniffing für Funktionen gilt, können diese Module unter den gleichen unerwarteten Leistungsänderungen leiden, die üblicherweise mit gespeicherten Prozeduren verbunden sind.
Wenn beispielsweise zum ersten Mal auf eine Funktion verwiesen wird, wird möglicherweise ein Plan zwischengespeichert, der keine Parallelität verwendet. Nachfolgende Ausführungen mit Parameterwerten, die von Parallelität profitieren würden (aber den zwischengespeicherten seriellen Plan wiederverwenden), zeigen eine unerwartet schlechte Leistung.
Dieses Problem kann schwierig zu identifizieren sein, da SQL Server keine separaten Post-Execution-Pläne für Funktionsaufrufe zurückgibt, wie wir gesehen haben. Verwendung von Erweiterten Ereignissen oder Profiler Die routinemäßige Erfassung von Post-Execution-Plänen kann sehr ressourcenintensiv sein, daher ist es oft sinnvoll, diese Technik sehr gezielt einzusetzen. Aufgrund der Schwierigkeiten beim Debuggen von Funktionsparameter-Empfindlichkeitsproblemen lohnt es sich umso mehr, eine Analyse durchzuführen (und defensiv zu codieren), bevor die Funktion in Produktion geht.
Das Parameter-Sniffing funktioniert auf die gleiche Weise mit benutzerdefinierten Skalarfunktionen von T-SQL (sofern nicht eingebettet, ab SQL Server 2019). Inline-Tabellenwertfunktionen generieren keinen separaten Ausführungsplan für jeden Aufruf, da diese (wie der Name schon sagt) vor der Kompilierung in die aufrufende Abfrage eingebettet werden.
Vorsicht vor geschnüffelten NULLen
Leeren Sie den Plan-Cache und fordern Sie eine Schätzung an (Vorausführungs-)Plan für die Testabfrage:
SELECT Result.ProductID, Result.Name, Result.TotalQty FROM dbo.F(N'K%') AS Result;
Sie sehen zwei Ausführungspläne, von denen der zweite für den Funktionsaufruf ist:
Eine Einschränkung des Parameter-Sniffing mit eingebetteten Funktionen in geschätzten Plänen bedeutet, dass der Parameterwert als NULL
geschnüffelt wird (nicht „K%“):
In Versionen von SQL Server vor 2012 war dieser Plan (optimiert für einen NULL
-Parameter) wird zur Wiederverwendung zwischengespeichert . Das ist bedauerlich, weil NULL
ist wahrscheinlich kein repräsentativer Parameterwert, und es war sicherlich nicht der in der Abfrage angegebene Wert.
SQL Server 2012 (und höher) speichert keine Pläne, die aus einer „geschätzten Plan“-Anforderung resultieren, obwohl es immer noch einen Funktionsplan anzeigt, der für einen NULL
optimiert ist Parameterwert zur Kompilierzeit.
Einfache und erzwungene Parametrisierung
Eine Ad-hoc-T-SQL-Anweisung, die konstante Literalwerte enthält, kann von SQL Server parametrisiert werden, entweder weil die Abfrage für eine einfache Parametrisierung geeignet ist oder weil die Datenbankoption für erzwungene Parametrisierung aktiviert ist (oder eine Planhinweisliste mit demselben Effekt verwendet wird).
Eine derart parametrisierte Anweisung unterliegt ebenfalls dem Parameter Sniffing. Die folgende Abfrage eignet sich für eine einfache Parametrisierung:
SELECT A.AddressLine1, A.City, A.PostalCode FROM Person.Address AS A WHERE A.AddressLine1 = N'Heidestieg Straße 8664';
Der geschätzte Ausführungsplan zeigt eine Schätzung von 2,5 Zeilen basierend auf dem ermittelten Parameterwert:
Tatsächlich gibt die Abfrage 7 Zeilen zurück (die Kardinalitätsschätzung ist nicht perfekt, selbst wenn Werte geschnüffelt werden):
An dieser Stelle fragen Sie sich vielleicht, wo der Beweis dafür ist, dass diese Abfrage parametrisiert und der resultierende Parameterwert geschnüffelt wurde. Führen Sie die Abfrage ein zweites Mal mit einem anderen Wert aus:
SELECT A.AddressLine1, A.City, A.PostalCode FROM Person.Address AS A WHERE A.AddressLine1 = N'Winter der Böck 8550';
Die Abfrage gibt eine Zeile zurück:
Der Ausführungsplan zeigt, dass die zweite Ausführung den parametrisierten Plan wiederverwendet hat, der mit einem Sniff-Wert kompiliert wurde:
Parametrieren und schnüffeln sind getrennte Aktivitäten
Eine Ad-hoc-Anweisung kann von SQL Server ohne parametrisiert werden Parameterwerte, die geschnüffelt werden.
Zur Demonstration können wir das Ablaufverfolgungsflag 4136 verwenden, um das Parameter-Sniffing für einen Stapel zu deaktivieren, der vom Server parametrisiert wird:
DBCC FREEPROCCACHE; DBCC TRACEON (4136); GO SELECT A.AddressLine1, A.City, A.PostalCode FROM Person.Address AS A WHERE A.AddressLine1 = N'Heidestieg Straße 8664'; GO SELECT A.AddressLine1, A.City, A.PostalCode FROM Person.Address AS A WHERE A.AddressLine1 = N'Winter der Böck 8550'; GO DBCC TRACEOFF (4136);
Das Skript führt zu parametrisierten Anweisungen, aber der Parameterwert wird nicht zum Zweck der Kardinalitätsschätzung geschnüffelt. Um dies zu sehen, können wir den Plan-Cache untersuchen:
WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan') SELECT DECP.cacheobjtype, DECP.objtype, DECP.usecounts, DECP.plan_handle, parameterized_plan_handle = DEQP.query_plan.value ( '(//StmtSimple)[1]/@ParameterizedPlanHandle', 'NVARCHAR(100)' ) FROM sys.dm_exec_cached_plans AS DECP CROSS APPLY sys.dm_exec_sql_text(DECP.plan_handle) AS DEST CROSS APPLY sys.dm_exec_query_plan(DECP.plan_handle) AS DEQP WHERE DEST.[text] LIKE N'%AddressLine1%' AND DEST.[text] NOT LIKE N'%XMLNAMESPACES%';
Die Ergebnisse zeigen zwei Cache-Einträge für die Ad-hoc-Abfragen, die durch das parametrisierte Plan-Handle mit dem parametrisierten (vorbereiteten) Abfrageplan verknüpft sind.
Der parametrisierte Plan wird zweimal verwendet:
Der Ausführungsplan zeigt nun eine andere Kardinalitätsschätzung, da das Parameter-Sniffing deaktiviert ist:
Vergleichen Sie die Schätzung von 1,44571 Zeilen mit der Schätzung von 2,5 Zeilen, die bei aktiviertem Parameter-Sniffing verwendet wurde.
Bei deaktiviertem Sniffing stammt die Schätzung aus Informationen zur durchschnittlichen Häufigkeit von AddressLine1
Säule. Ein Auszug aus DBCC SHOW_STATISTICS
Die Ausgabe für den betreffenden Index zeigt, wie diese Zahl berechnet wurde:Die Multiplikation der Anzahl der Zeilen in der Tabelle (19.614) mit der Dichte (7,370826e-5) ergibt die Schätzung von 1,44571 Zeilen.
Nebenbemerkung: Es wird allgemein angenommen, dass nur ganzzahlige Vergleiche mit einem eindeutigen Index für eine einfache Parametrisierung geeignet sind. Ich habe bewusst dieses Beispiel gewählt (ein Zeichenkettenvergleich mit einem nicht eindeutigen Index), um das zu widerlegen.
MIT RECOMPILE und OPTION (RECOMPILE)
Wenn ein Parameterempfindlichkeitsproblem auftritt, lautet ein allgemeiner Ratschlag in Foren und Q&A-Sites, „recompile“ zu verwenden (vorausgesetzt, die anderen zuvor vorgestellten Tuning-Optionen sind ungeeignet). Leider wird dieser Ratschlag oft dahingehend fehlinterpretiert, dass er das Hinzufügen von WITH RECOMPILE
bedeutet Option für die gespeicherte Prozedur.
Verwenden von WITH RECOMPILE
effektiv bringt uns das Verhalten von SQL Server 2000 zurück, wo die gesamte gespeicherte Prozedur wird bei jeder Ausführung neu kompiliert.
Eine bessere Alternative , auf SQL Server 2005 und höher, ist die Verwendung der OPTION (RECOMPILE)
Abfragehinweis nur auf die Anweisung das unter dem Parameter-Sniffing-Problem leidet. Dieser Abfragehinweis führt zu einer Neukompilierung der problematischen Anweisung nur. Ausführungspläne für andere Anweisungen innerhalb der gespeicherten Prozedur werden zwischengespeichert und wie gewohnt wiederverwendet.
Verwenden von WITH RECOMPILE
bedeutet auch, dass der kompilierte Plan für die gespeicherte Prozedur nicht zwischengespeichert wird. Daher werden in DMVs wie sys.dm_exec_query_stats
keine Leistungsinformationen verwaltet .
Die Verwendung des Abfragehinweises bedeutet stattdessen, dass ein kompilierter Plan zwischengespeichert werden kann und Leistungsinformationen in den DMVs verfügbar sind (obwohl sie auf die letzte Ausführung beschränkt sind, nur für die betroffene Anweisung).
Verwenden Sie für Instanzen, auf denen mindestens SQL Server 2008 Build 2746 (Service Pack 1 mit kumulativem Update 5) ausgeführt wird, OPTION (RECOMPILE)
hat einen weiteren signifikanten Vorteil über WITH RECOMPILE
:Nur OPTION (RECOMPILE)
aktiviert die Optimierung der Parametereinbettung .
Die Optimierung der Parametereinbettung
Durch das Sniffing von Parameterwerten kann der Optimierer den Parameterwert verwenden, um Kardinalitätsschätzungen abzuleiten. Beide WITH RECOMPILE
und OPTION (RECOMPILE)
führen zu Abfrageplänen mit Schätzungen, die bei jeder Ausführung aus den tatsächlichen Parameterwerten berechnet werden.
Die Parametereinbettungsoptimierung geht diesen Prozess noch einen Schritt weiter. Abfrageparameter werden ersetzt mit wörtlichen konstanten Werten während der Abfrageanalyse.
Der Parser ist zu überraschend komplexen Vereinfachungen in der Lage, und eine anschließende Abfrageoptimierung kann die Dinge noch weiter verfeinern. Betrachten Sie die folgende gespeicherte Prozedur, die WITH RECOMPILE
enthält Möglichkeit:
CREATE PROCEDURE dbo.P @NameLike nvarchar(50), @Sort tinyint WITH RECOMPILE AS BEGIN SELECT TOP (5) ProductID, Name FROM Production.Product WHERE @NameLike IS NULL OR Name LIKE @NameLike ORDER BY CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC, CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC, CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC, CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC; END;
Die Prozedur wird zweimal ausgeführt, mit den folgenden Parameterwerten:
EXECUTE dbo.P @NameLike = N'K%', @Sort = 1; GO EXECUTE dbo.P @NameLike = N'[H-R]%', @Sort = 4;
Denn WITH RECOMPILE
verwendet wird, wird die Prozedur bei jeder Ausführung vollständig neu kompiliert. Die Parameterwerte werden erschnüffelt und vom Optimierer zur Berechnung der Kardinalitätsschätzungen verwendet.
Der Plan für die erste Prozedurausführung ist genau richtig und schätzt 1 Zeile:
Die zweite Ausführung schätzt 360 Zeilen, sehr nahe an den 366 Zeilen, die zur Laufzeit angezeigt werden:
Beide Pläne verwenden die gleiche allgemeine Ausführungsstrategie:Scannen Sie alle Zeilen in einem Index, indem Sie den WHERE
anwenden Klauselprädikat als Rest; Berechnen Sie den CASE
Ausdruck, der in ORDER BY
verwendet wird Klausel; und führen Sie eine Top-N-Sortierung durch auf das Ergebnis von CASE
Ausdruck.
OPTION (NEUKOMPILIEREN)
Erstellen Sie nun die gespeicherte Prozedur mit einem OPTION (RECOMPILE)
neu Abfragehinweis statt WITH RECOMPILE
:
CREATE PROCEDURE dbo.P @NameLike nvarchar(50), @Sort tinyint AS BEGIN SELECT TOP (5) ProductID, Name FROM Production.Product WHERE @NameLike IS NULL OR Name LIKE @NameLike ORDER BY CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC, CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC, CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC, CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC OPTION (RECOMPILE); END;
Das zweimalige Ausführen der gespeicherten Prozedur mit denselben Parameterwerten wie zuvor führt zu dramatisch unterschiedlichen Ergebnissen Ausführungspläne.
Dies ist der erste Ausführungsplan (mit Parametern, die Namen anfordern, die mit „K“ beginnen, sortiert nach ProductID
aufsteigend):
Der Parser bettet ein die Parameterwerte im Abfragetext, was zu folgender Zwischenform führt:
SELECT TOP (5) ProductID, Name FROM Production.Product WHERE 'K%' IS NULL OR Name LIKE 'K%' ORDER BY CASE WHEN 1 = 1 THEN ProductID ELSE NULL END ASC, CASE WHEN 1 = 2 THEN ProductID ELSE NULL END DESC, CASE WHEN 1 = 3 THEN Name ELSE NULL END ASC, CASE WHEN 1 = 4 THEN Name ELSE NULL END DESC;
Der Parser geht dann weiter, entfernt Widersprüche und wertet den CASE
vollständig aus Ausdrücke. Daraus ergibt sich:
SELECT TOP (5) ProductID, Name FROM Production.Product WHERE Name LIKE 'K%' ORDER BY ProductID ASC, NULL DESC, NULL ASC, NULL DESC;
Sie erhalten eine Fehlermeldung, wenn Sie versuchen, diese Abfrage direkt an SQL Server zu senden, da das Sortieren nach einem konstanten Wert nicht zulässig ist. Trotzdem ist dies die vom Parser erzeugte Form. Es ist intern erlaubt, da es durch die Anwendung der Parametereinbettungsoptimierung entstanden ist . Die vereinfachte Abfrage erleichtert dem Abfrageoptimierer das Leben erheblich:
Der Clustered Index Scan wendet das LIKE
an Prädikat als Rest. Der Rechenskalar liefert die Konstante NULL
Werte. Das Oben gibt die ersten 5 Zeilen in der vom Clustered Index bereitgestellten Reihenfolge zurück (Vermeidung einer Sorte). In einer perfekten Welt würde der Abfrageoptimierer auch den Compute Scalar entfernen die die NULLs
definiert , da sie während der Abfrageausführung nicht verwendet werden.
Die zweite Ausführung folgt genau dem gleichen Prozess, was zu einem Abfrageplan führt (für Namen, die mit den Buchstaben „H“ bis „R“ beginnen, sortiert nach Name
absteigend) wie folgt:
Dieser Plan beinhaltet eine Nonclustered Index Seek die das LIKE
abdeckt Bereich, ein Rest LIKE
Prädikat, die konstanten NULLs
wie zuvor und ein Top (5). Der Abfrageoptimierer entscheidet sich für ein BACKWARD
Bereichsscan in der Indexsuche um erneut das Sortieren zu vermeiden.
Vergleichen Sie den obigen Plan mit dem, der mit WITH RECOMPILE
erstellt wurde , die die Parametereinbettungsoptimierung nicht verwenden können :
Dieses Demo-Beispiel hätte besser als eine Reihe von IF
implementiert werden können Anweisungen in der Prozedur (eine für jede Kombination von Parameterwerten). Dies könnte ähnliche Vorteile für den Abfrageplan bieten, ohne dass jedes Mal eine Anweisungskompilierung erforderlich wäre. In komplexeren Szenarien erfolgt die Neukompilierung auf Anweisungsebene mit Parametereinbettung, die von OPTION (RECOMPILE)
bereitgestellt wird kann eine äußerst nützliche Optimierungstechnik sein.
Eine Einbettungsbeschränkung
Es gibt ein Szenario, in dem OPTION (RECOMPILE)
verwendet wird führt nicht dazu, dass die Optimierung der Parametereinbettung angewendet wird. Wenn die Anweisung einer Variablen zuweist, werden Parameterwerte nicht eingebettet:
CREATE PROCEDURE dbo.P @NameLike nvarchar(50), @Sort tinyint AS BEGIN DECLARE @ProductID integer, @Name nvarchar(50); SELECT TOP (1) @ProductID = ProductID, @Name = Name FROM Production.Product WHERE @NameLike IS NULL OR Name LIKE @NameLike ORDER BY CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC, CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC, CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC, CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC OPTION (RECOMPILE); END;
Da die SELECT
-Anweisung nun einer Variablen zuweist, sind die erzeugten Abfragepläne die gleichen wie bei WITH RECOMPILE
wurde benutzt. Parameterwerte werden weiterhin geschnüffelt und vom Abfrageoptimierer für die Kardinalitätsschätzung und OPTION (RECOMPILE)
verwendet kompiliert immer noch nur die einzelne Anweisung, nur der Vorteil der Parametereinbettung ist verloren.