Warum verbessert OPTIMIZE FOR UNKNOWN meine Abfrage um einige Sekunden?


7

Ok, ich habe eine Abfrage für nicht gespeicherte Prozeduren, die wir in einem SSRS-Bericht verwenden. Diese Abfrage war höllisch langsam (ich hatte die ursprüngliche Version dieser Abfrage in den letzten zwei Stunden ausgeführt, immer noch nicht ausgeführt). Um sie zu verbessern, habe ich sie von Grund auf neu geschrieben und mir Folgendes ausgedacht:

Hier ist der langweilige Teil des Wortproblems:

Wir wollen eine Liste der ziehen TOP 5Kunden pro Vertriebsmitarbeiter, aber ausschließen die TOP 10Gesamt Kunden aus dieser Liste. (Wenn John Doe also Clients A, B, C, D und E hat und Client C zu den Top 10 gehört, ziehen Sie nur A, B, D und E.)

Um dies zu tun, verwendete die erste Abfrage ein IN (... NOT IN ( ) ), also dachte ich, dass das Verschachteln von INdas Problem war, um es umzuschreiben, tat ich ein OUTER APPLY, das wirklich alles kaputt machte.

Wie auch immer, ich habe das alles behoben und die Abfrage ausgeführt, und es dauerte immer noch 10-15 Sekunden, von denen ich annahm, dass es sich um Parameter-Sniffing handelte. Zur Untersuchung habe ich die Abfrage in SSMS ausgeführt, hinzugefügt OPTION (RECOMPILE)(um zu sehen, welcher Abfrageplan generiert wird) und Folgendes erhalten:

Plan 1

Es kann hier auf Brent Ozars 'Paste The Plan' angesehen werden . Die Abfrage, die dies generiert hat, war:

DECLARE @Top10Temp TABLE (Id INT)
INSERT INTO @Top10Temp
SELECT TOP 10 Id
FROM Object1
WHERE Column2  = @ReportId
  AND Column3  = 0
GROUP BY Id
ORDER BY SUM(Column4 + Column5) DESC

SELECT Object2.*
FROM Object1 AS Object2
OUTER APPLY (
    SELECT TOP 5
        Object3.Id,
        SUM(Object3.Column4 + Object3.Column5) AS Column6
    FROM Object1 AS Object3
    WHERE Object3.Column3 = 0
      AND Object3.Column7 = Object2.Column7
      AND Object3.Column2 = @ReportId
    GROUP BY
        Object3.Id
    ORDER BY
        SUM(Object3.Column4 + Object3.Column5) DESC
) AS Object4
WHERE Object2.Column2 =      @ReportId
  AND Object2.Column3 =      0
  AND Object2.Id      =      Object4.Id
  AND Object2.Id      NOT IN (SELECT Id FROM @Top10Temp)
ORDER BY Object2.Column7
OPTION (RECOMPILE)

Jetzt hat die gleiche Abfrage aber mit OPTION (OPTIMIZE FOR UNKNOWN)folgendem Plan generiert:

Plan 2

Was auch unter "Plan einfügen" angezeigt werden kann . Dieser Plan wurde in weniger als 1 Sekunde ausgeführt.

Wenn ich hinzufüge OPTION (OPTIMIZE FOR (@ReportId = #)), wobei #das gleiche wie die @ReportIdVariable ist, erhalte ich den gleichen Abfrageplan wie das zweite.

Habe ich etwas falsch gemacht? Ich habe Probleme zu verstehen, was passiert ist, daher werden alle Informationen sehr geschätzt. (Ich mag es auch nicht, den Optimierer über Hinweise zu beeinflussen, aber wenn es nötig ist, werde ich es behalten.)


Haben Sie versucht, die Statistiken für die betreffenden Tabellen zu aktualisieren?
Max Vernon

@ MaxVernon Ja, und es hat eigentlich nichts geholfen. : /
Der Kommissar

Antworten:


5

"Um das zu untersuchen, habe ich die Abfrage in SSMS ausgeführt ..." Das ist das Problem. Lokale Variablen verwenden den Dichtevektor von Statistiken, der eine viel bessere Zeilenschätzung ergibt und folglich bereits für UNBEKANNT OPTIMIERT. Parametrisiertes dynamisches SQL verwendet das Histogramm, das die gesamte Zeilenanzahl für einen bestimmten Abschnitt abruft.

Sehen Sie sich die geschätzte Anzahl und die tatsächliche Anzahl der Zeilen für jeden Ihrer Links zum Einfügen des Plans an. Der zweite Link hat waaaayyyy bessere Schätzungen als der erste.

Ich würde Ihre SSRS-Abfrage auf einer Entwicklungsinstanz bereitstellen und einige Tests ausführen, da ich vermute, dass Sie Leistungsprobleme haben könnten.

Übrigens, aktualisieren Sie Statistiken oder erstellen Sie Indizes für diese Bestientabellen neu, wenn Sie können.

Links: Innerhalb des Statistikhistogramms und des Dichtevektors


Die Sache ist, dass diese Tabellen nicht so groß sind (hätte das der Frage hinzufügen sollen), die Gesamtzahl der Zeilen nur 200.000 beträgt und die Abfrage eine endgültige Zeilenanzahl von etwa 1850 Zeilen zurückgibt.
Der Kommissar

@ 202_accepted Selbst wenn die Tabelle nicht groß ist, können ungenaue Schätzungen der zurückgegebenen Zeilen zu übermäßigen Speicherzuweisungen führen, die möglicherweise zu Konflikten auf dem Server führen können. Wenn der Server unter Ressourcenkonflikten steht, ist die Priorität der Anforderungen umso geringer, je größer die Speicherzuweisung ist. Sehen Sie sich Ihren ersten Ausführungsplan an (bewegen Sie den Mauszeiger über die Indexsuchen oder -scans) und vergleichen Sie die geschätzten mit den tatsächlich zurückgegebenen Zeilen. Sie sind verrückt! Vergleichen Sie das jetzt mit dem 2. Ausführungsplan: nicht großartig, aber viel besser auf die tatsächlichen Verhältnisse zu schätzen. Zuletzt testen Sie den Aufruf der Abfrage von SSRS und lokal: anders?
Thundercougarfalcon

4

Der langsame Plan weist eine schlechte Kardinalitätsschätzung auf, die aus der Indexsuche an Knoten 4 hervorgeht. Die geschätzte Anzahl von Zeilen beträgt 1, aber die tatsächliche Anzahl von Zeilen beträgt 3261. Hier ist das Suchprädikat:

Seek Keys[1]: Prefix: Database1.Schema1.Object1.Column2, Database1.Schema1.Object1.Column3 = Scalar Operator(ScalarString7), Scalar Operator(ScalarString2)

Sie filtern nach zwei verschiedenen Spalten aus derselben Tabelle. Häufig verfügt SQL Server nicht über genügend Informationen, um eine genaue Schätzung für dieses Szenario abzugeben. Daher werden Modellierungsannahmen getroffen, die von Ihrer CE-Version, Patches, Ablaufverfolgungsflags usw. abhängen. Beispielsweise könnte angenommen werden, dass die Spalten keine Korrelation aufweisen, und die Selektivitäten miteinander multipliziert werden. Dies kann zu einer geringen Schätzung führen, wenn eine Korrelation zwischen den Filtern besteht.

Im Allgemeinen würde ich sagen, wenn Sie eine gute Leistung mit einer schlechten Schätzung erzielen, haben Sie wahrscheinlich Glück und Ihr Glück kann irgendwann ausgehen. Ich würde versuchen, diese Schätzung zu korrigieren. Ich kann Ihnen keine genauen Anweisungen geben, da zu viele Informationen fehlen (Sie können einige der fehlenden Informationen aufgrund von IP-Bedenken nicht weitergeben), aber ich kann sagen, dass eine mehrspaltige Statistik oder ein mehrspaltiger Index hilfreich sein könnte. Das Speichern der Primärschlüssel der Tabelle nach dem Filtern in eine temporäre Tabelle sollte immer funktionieren. Mit einer genaueren Schätzung würde ich einen Abfrageplan erwarten, der dem schnellen Plan ähnlich ist.

Sie haben nichts falsch gemacht, indem Sie den OPTION (RECOMPILE)Hinweis hinzugefügt haben . Möglicherweise haben Sie nur durch Pech eine schlechte Leistung erzielt. Die Optimierung der Parametereinbettung hilft normalerweise, anstatt Probleme zu verursachen. OPTIMIZE FOR UNKNOWNbewirkt, dass SQL Server die Statistikobjekte anders verwendet, und es kommt nur so vor, dass Sie bei der Verwendung eine Schätzung erhalten, die der Realität näher kommt.

Ich würde nicht OPTIMIZE FOR UNKNOWNals langfristige Lösung verwenden. Der Abfrageplan ändert sich nicht, je nachdem, @ReportIdwelcher Wert Probleme verursachen kann, wenn Sie den Wert der Variablen ändern. Es ist auch eine indirekte Lösung, und Sie haben zugegeben, dass Sie nicht verstehen, wie es funktioniert. Es wäre besser, das Problem direkter anzugreifen, indem Sie die Kardinalitätsschätzung festlegen oder Zwischenergebnisse strategisch in temporären Tabellen materialisieren. In der Regel sollten Sie die Verwendung von Tabellenvariablen vermeiden, da diese keine Statistiken enthalten. Tabellenvariablen haben nur sehr begrenzte Anwendungsfälle, und ich empfehle Ihnen, sie nur zu verwenden, wenn Sie keine andere Wahl haben.


Ich schätze diese detaillierte Antwort, Joe, und ich habe nur eine Frage: Gibt es eine Möglichkeit, SQL Server darauf hinzuweisen, dass jeder @ReportIddie gleiche Anzahl von Ergebnissen hat? (Sie sollten innerhalb von 5 bis 20 Reihen voneinander liegen.) Ich weiß nicht, ob dies meiner Situation helfen würde oder nicht oder ob es überhaupt möglich ist, aber das ist eine Sache, die ich als Auswirkung sehen konnte.
Der Kommissar

1
@ 202_accepted Fragen Sie nur nach der Spalte, nach der ReportId filtert? Wenn das Histogramm für die Spalte Ihre Daten gut genug darstellt, sollte dies nicht erforderlich sein. Andernfalls könnten Sie versuchen, einen repräsentativen Wert für OPTIMIZE FOR VALUE auszuwählen, aber Ihr Histogramm könnte sich im Laufe der Zeit ändern. Wenn Sie Dichte verwenden möchten, können Sie OPTIMIZE FOR UNKNOWN zusammen mit OPTIMIZE FOR VALUE für alles andere verwenden, aber dafür ist der Hinweis wohl nicht gedacht.
Joe Obbish

Ich stimme Joe zu. Suchen Sie nach Plananleitung für mir Details und Optionen. technet.microsoft.com/en-us/library/…
Magier
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.