Wie kann man die Kosten für die Erstellung eines Abfrageplans messen oder ermitteln?

18

Ich habe einen typischen Fall, in dem Parameter-Sniffing dazu führt, dass ein "fehlerhafter" Ausführungsplan im Plan-Cache landet und nachfolgende Ausführungen meiner gespeicherten Prozedur sehr langsam sind. Ich kann dieses Problem mit lokalen Variablen "lösen" OPTIMIZE FOR ... UNKNOWN, und OPTION(RECOMPILE). Ich kann mich jedoch auch mit der Abfrage befassen und versuchen, sie zu optimieren.

Ich versuche herauszufinden, ob ich es sollte : Angesichts der begrenzten Zeit, um Probleme zu beheben, würde ich gerne wissen, welche Kosten es kostet, dies nicht zu tun. Aus meiner Sicht OPTION(RECOMPILE)besteht der Nettoeffekt darin, dass ein Abfrageplan jedes Mal neu erstellt wird, wenn die Abfrage ausgeführt wird. Also, ich denke ich muss wissen:

Wie können Sie die Kosten für die Erstellung eines Abfrageplans ermitteln?

Um meine eigene Frage zu beantworten, habe ich gegoogelt (z. B. mit dieser Abfrage ) und die Dokumentation der Spalten für die dm_exec_query_statsDMV durchgesehen . Ich habe auch das Ausgabefenster in SSMS nach "Actual Query Plan" durchsucht, um diese Informationen zu finden. Schließlich habe ich DBA.SE gesucht . Keiner von diesen führte zu einer Antwort.

Kann mir jemand sagen? Ist es möglich, die für die Planerstellung benötigte Zeit zu finden oder zu messen?

Jeroen
quelle
5
Ich würde empfehlen, eine Kopie von Inside the SQL Server Query Optimizer von Benjamin Nevarez zu kaufen . Es ist kostenlos. Kapitel 5 'Der Optimierungsprozess' kann Ihnen dabei helfen, die Kompilierungszeit für Ihre Abfrage zu ermitteln. Zumindest ist es informativ darüber, was das Optimierungsprogramm durchläuft, um einen Abfrageplan zu erstellen.
Mark Sinkinson

Antworten:

18

Wie können Sie die Kosten für die Erstellung eines Abfrageplans ermitteln?

Sie können die Eigenschaften des Stammknotens im Abfrageplan anzeigen, zum Beispiel:

Wurzeleigenschaften extrahieren
(Screenshot aus dem kostenlosen Sentry One Plan Explorer )

Diese Informationen stehen auch zur Verfügung, indem Sie den Plan-Cache abfragen, z. B. mithilfe einer Abfrage, die auf den folgenden Beziehungen basiert:

WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    CompileTime = c.value('(QueryPlan/@CompileTime)[1]', 'int'),
    CompileCPU = c.value('(QueryPlan/@CompileCPU)[1]', 'int'),
    CompileMemory = c.value('(QueryPlan/@CompileMemory)[1]', 'int'),
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_query_plan(CP.plan_handle) AS QP
CROSS APPLY sys.dm_exec_sql_text(CP.plan_handle) AS ST
CROSS APPLY QP.query_plan.nodes('ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS N(c);

Ergebnisfragment

Eine vollständige Beschreibung der Optionen, die Sie zur Bearbeitung dieser Art von Abfragen haben, finden Sie im kürzlich aktualisierten Artikel von Erland Sommarskog .

Paul White sagt GoFundMonica
quelle
4

Angenommen, "Kosten" sind in Bezug auf die Zeit (obwohl Sie nicht sicher sind, was es sonst in Bezug auf ;-) sein könnte, sollten Sie zumindest in der Lage sein, sich ein Bild davon zu machen, indem Sie Folgendes tun:

DBCC FREEPROCCACHE WITH NO_INFOMSGS;

SET STATISTICS TIME ON;

EXEC sp_help 'sys.databases'; -- replace with your proc

SET STATISTICS TIME OFF;

Der erste auf der Registerkarte "Nachrichten" gemeldete Artikel sollte sein:

SQL Server-Analyse- und Kompilierungszeit:

Ich würde dies mindestens 10 Mal ausführen und sowohl die "CPU" - als auch die "verstrichene" Millisekunde mitteln.

Im Idealfall würden Sie dies in der Produktion ausführen, um eine echte Zeitschätzung zu erhalten, aber nur selten dürfen Benutzer den Plan-Cache in der Produktion leeren. Glücklicherweise wurde es ab SQL Server 2008 möglich, einen bestimmten Plan aus dem Cache zu löschen. In diesem Fall können Sie Folgendes tun:

DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
  SELECT DISTINCT stat.plan_handle
  FROM sys.dm_exec_query_stats stat
  CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
  WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
               + CONVERT(NVARCHAR(130), cte.plan_handle, 1)
               + N');'
               + NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);

SET STATISTICS TIME ON;

EXEC sp_help 'sys.databases' -- replace with your proc

SET STATISTICS TIME OFF;

Abhängig von der Variabilität der Werte, die für die Parameter übergeben werden, die den "fehlerhaften" zwischengespeicherten Plan verursachen, ist jedoch eine andere Methode zu berücksichtigen, die einen Mittelweg zwischen OPTION(RECOMPILE)und darstellt OPTION(OPTIMIZE FOR UNKNOWN): Dynamic SQL. Ja, ich habe es gesagt. Und ich meine sogar nicht parametrisiertes dynamisches SQL. Hier ist warum.

Sie haben eindeutig Daten mit einer ungleichmäßigen Verteilung, zumindest in Bezug auf einen oder mehrere Eingabeparameterwerte. Die Nachteile der genannten Optionen sind:

  • OPTION(RECOMPILE)Für jede Ausführung wird ein Plan erstellt, und Sie können niemals von einer Wiederverwendung des Plans profitieren , selbst wenn die erneut übergebenen Parameterwerte mit den vorherigen Läufen identisch sind. Bei Prozessen, die häufig aufgerufen werden - mindestens alle paar Sekunden -, werden Sie vor der gelegentlichen schrecklichen Situation bewahrt, bleiben jedoch in einer immer nicht allzu großartigen Situation.

  • OPTION(OPTIMIZE FOR (@Param = value)) wird einen Plan erstellen, der auf diesem bestimmten Wert basiert. Dies könnte in mehreren Fällen hilfreich sein, lässt Sie jedoch weiterhin für das aktuelle Problem offen.

  • OPTION(OPTIMIZE FOR UNKNOWN)Es wird ein Plan erstellt, der auf einer durchschnittlichen Verteilung basiert, die einigen Abfragen hilft, anderen jedoch schadet. Dies sollte mit der Option zur Verwendung lokaler Variablen identisch sein.

Bei korrekter Ausführung von Dynamic SQL haben die verschiedenen übergebenen Werte jedoch ihre eigenen, idealen Abfragepläne (so gut sie auch sein werden). Die Hauptkosten hierbei sind, dass mit zunehmender Anzahl der übergebenen Werte die Anzahl der Ausführungspläne im Cache zunimmt und sie Speicher belegen. Die Nebenkosten sind:

  • String-Parameter müssen validiert werden, um SQL-Injections zu verhindern

  • Möglicherweise müssen Sie ein Zertifikat und einen zertifikatbasierten Benutzer einrichten, um die ideale Sicherheitsabstraktion aufrechtzuerhalten, da für Dynamic SQL direkte Tabellenberechtigungen erforderlich sind.

So habe ich diese Situation gemeistert, als ich Procs hatte, die mehr als einmal pro Sekunde aufgerufen wurden und mehrere Tabellen mit jeweils Millionen von Zeilen trafen. Ich hatte es versucht, OPTION(RECOMPILE)aber dies erwies sich in 99% der Fälle, in denen der Parameter Sniffing / Bad-Cached-Plan-Problem nicht auftrat, als viel zu schädlich für den Prozess. Beachten Sie bitte, dass in einem dieser Prozesse ungefähr 15 Abfragen enthalten waren und nur 3 bis 5 davon wie hier beschrieben in dynamisches SQL konvertiert wurden. Dynamisches SQL wurde nur verwendet, wenn es für eine bestimmte Abfrage erforderlich war.

  1. Wenn die gespeicherte Prozedur mehrere Eingabeparameter enthält, ermitteln Sie, welche mit Spalten mit sehr unterschiedlichen Datenverteilungen verwendet werden (und daher dieses Problem verursachen) und welche mit Spalten mit gleichmäßigeren Verteilungen verwendet werden (und nicht sollten) dieses Problem verursachen).

  2. Erstellen Sie die Dynamic SQL-Zeichenfolge mit Parametern für die Proc-Eingabeparameter, die gleichmäßig verteilten Spalten zugeordnet sind. Diese Parametrisierung trägt dazu bei, die resultierende Zunahme der Ausführungspläne im Cache für diese Abfrage zu reduzieren.

  3. Für die übrigen Parameter, die mit sehr unterschiedlichen Verteilungen verknüpft sind, sollten diese als Literalwerte in Dynamic SQL verkettet werden. Da eine eindeutige Abfrage durch Änderungen am Abfragetext bestimmt wird, WHERE StatusID = 1ist having eine andere Abfrage und damit ein anderer Abfrageplan als having WHERE StatusID = 2.

  4. Handelt es sich bei einem der Proc-Eingabeparameter, die in den Text der Abfrage eingebunden werden sollen, um Zeichenfolgen, müssen diese zum Schutz vor SQL Injection validiert werden (dies ist jedoch weniger wahrscheinlich, wenn die übergebenen Zeichenfolgen vom generiert werden App und kein Benutzer, aber immer noch). Zumindest tun Sie dies, REPLACE(@Param, '''', '''''')um sicherzustellen, dass einfache Anführungszeichen zu einfachen Anführungszeichen werden.

  5. Erstellen Sie bei Bedarf ein Zertifikat, das zum Erstellen eines Benutzers verwendet wird, und signieren Sie die gespeicherte Prozedur so, dass direkte Tabellenberechtigungen nur dem neuen zertifikatbasierten Benutzer und nicht [public]oder nur Benutzern gewährt werden, die ansonsten nicht über solche Berechtigungen verfügen sollten .

Beispiel proc:

CREATE PROCEDURE MySchema.MyProc
(
  @Param1 INT,
  @Param2 DATETIME,
  @Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'
     SELECT  tab.Field1, tab.Field2, ...
     FROM    MySchema.SomeTable tab
     WHERE   tab.Field3 = @P1
     AND     tab.Field8 >= CONVERT(DATETIME, ''' +
  CONVERT(NVARCHAR(50), @Param2, 121) +
  N''')
     AND     tab.Field2 LIKE N''' +
  REPLACE(@Param3, N'''', N'''''') +
  N'%'';';

EXEC sp_executesql
     @SQL,
     N'@P1 INT',
     @P1 = @Param1;
Solomon Rutzky
quelle
Vielen Dank, dass Sie sich (einige) Zeit genommen haben, um zu antworten! Ich bin allerdings etwas skeptisch, was das erste Mal angeht, wie die Kompilierungszeit ermittelt wird, da sie um einen Faktor 3 niedriger ist als das Ergebnis, das ich mit dem @ PaulWhite-Ansatz erhalte . - Das zweite Dynamic SQL-Bit ist interessant (obwohl die Implementierung auch einige Zeit in Anspruch nehmen OPTIONwürde ; zumindest mehr als nur eine Antwort auf meine Abfrage) und würde mich nicht allzu sehr verletzen, da dieser Sproc in Integrationstests gut genutzt wird. - Auf jeden Fall: Danke für deine Einsichten!
Jeroen