Große Speichergewährungsanforderungen

7

Ich habe eine Abfrage mit mehreren Ausführungsplänen. Der einem Plan zugewiesene Speicher ist im Vergleich zum zweiten sehr groß

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

basierend auf diesem Artikel https://blogs.msdn.microsoft.com/sql_server_team/addressing-large-memory-grant-requests-from-optimized-nested-loops/

Das Problem tritt auf, wenn die äußere Tabelle des Nested-Loop-Joins ein Prädikat enthält, das das Ergebnis nach einer kleinen Eingabe filtert, die Stapelsortierung jedoch anscheinend eine Schätzung der Kardinalität verwendet, die der gesamten äußeren Tabelle entspricht. Dies kann zu einer vermeintlich übermäßigen Speicherzuweisung führen, die auf einem sehr gleichzeitigen Server verschiedene Nebenwirkungen haben kann, z. B. OOM-Bedingungen, Speicherdruck für die Räumung des Plan-Cache oder unerwartete Wartezeiten bei RESOURCE_SEMAPHORE. Wir haben gesehen, wie eine einzelne Abfrage, die diesem Muster entspricht, tatsächlich mehrere GB gewährten Speicher auf High-End-Computern (1 TB + RAM) erhalten kann.

Eine Möglichkeit bestand bisher darin, diese Funktion mithilfe des Ablaufverfolgungsflags 2340 global zu deaktivieren, wie in KB 2801413 beschrieben. In SQL Server 2016 RC0 haben wir das Verhalten jedoch geändert, um den Vorteil der Optimierung beizubehalten. Jetzt basiert das maximale Gewährungslimit auf dem verfügbaren Speicher Speicherplatz gewähren. Diese Verbesserung führt auch zu einer besseren Skalierbarkeit, da mehr Abfragen mit einem geringeren Speicherbedarf ausgeführt werden können. Wir versuchen, dieses Verhalten wieder auf ein bevorstehendes zu portieren. Wir haben dieses Verhalten auf SQL Server 2014 Service Pack 2 portiert und bieten wie üblich einen Mehrwert für In-Market-Versionen.

Dies ist genau das, was ich sehe, jedoch verwende ich SQL Server 2016 Enterprise.

Dies sind die Ausführungspläne

https://www.brentozar.com/pastetheplan/?id=SJ0mYAy0b

https://www.brentozar.com/pastetheplan/?id=BJzutC1R-

Meine Fragen sind

  1. Was ist der Grund für 2 Ausführungspläne?

  2. Das Optimierungsprogramm verwendet den Top-Ausführungsplan. Ich habe ihn gezwungen, den unteren Plan zu verwenden, aber nach einiger Zeit geht es wieder zum Top-Plan. Gibt es einen Grund dafür?

  3. Wie kann ich dieses Problem beheben? Dieses Problem führt zum Absturz der Anwendung (es gab viele RESOURCE_SEMAPHOREWartezeiten und die Anwendung reagiert nicht mehr). Soll ich den Hinweis verwenden: DISABLE_OPTIMIZED_NESTED_LOOPoder Trace Flag 2340?

    HINWEIS: Ich habe das XML überprüft und beide Pläne haben NestedLoops Optimized="false"

sebeid
quelle

Antworten:

7

1. Was ist der Grund für 2 Ausführungspläne?

Aus der Dokumentation zu sys.query_context_settings (Transact-SQL) :

In SQL Server stehen eine Reihe von Kontexteinstellungen zur Verfügung, die die Abfragesemantik beeinflussen (Definition des korrekten Ergebnisses der Abfrage). Derselbe Abfragetext, der unter verschiedenen Einstellungen kompiliert wurde, kann (abhängig von den zugrunde liegenden Daten) zu unterschiedlichen Ergebnissen führen.

Es ist sehr wahrscheinlich, dass derselbe Abfragetext aus Sitzungen mit unterschiedlichen Kontexteinstellungen gesendet wurde. Siehe auch Langsam in der Anwendung, Schnell in SSMS? von Erland Sommarskog.

2. Das Optimierungsprogramm verwendet den oberen Ausführungsplan. Ich habe ihn gezwungen, den unteren Plan zu verwenden, aber nach einiger Zeit geht es wieder zum oberen Plan. Gibt es einen Grund dafür?

Pläne werden weiterhin von Zeit zu Zeit neu kompiliert, basierend auf den normalen Regeln für Änderungen an z. B. zugrunde liegenden Objekten, Statistiken und Schemata. Durch das Erzwingen eines Abfragespeicherplans wird sichergestellt, dass der verwendete Plan dem Quellplan zumindest grundlegend ähnlich ist , wie in sys.query_store_plan (Transact-SQL) angegeben :

Der Forcing-Mechanismus garantiert nicht, dass genau dieser Plan für die Abfrage verwendet wird, auf die von query_id verwiesen wird . Durch das Erzwingen von Plänen wird die Abfrage erneut kompiliert und es wird normalerweise genau derselbe oder ein ähnlicher Plan erstellt wie auf den Plan, auf den plan_id verweist .

Jede (Neu-) Kompilierung kann Pläne mit unterschiedlichen Schätzungen (einschließlich Speicherzuweisung) erstellen, abhängig von den Werten in der Abfrage, die für den Optimierer zum Zeitpunkt der Kompilierung sichtbar sind. Eine bestimmte Speicherzuweisung kann nicht mithilfe von Abfragespeichern oder Planhandbüchern erzwungen werden.

3. Wie kann dieses Problem behoben werden?

Wie aus der Antwort von Dan Guzman hervorgeht , kann die Erstellung eines neuen Plans für jede Ausführung unter Verwendung eines OPTION (RECOMPILE)Hinweises die beste praktische Lösung sein , wenn der Plan sehr empfindlich auf bestimmte Parameterwerte reagiert .

Es stehen jedoch nur sehr wenige Tools zur Verfügung, um die Ausführungspläne auf der Aktualisierungsseite zu beeinflussen. Diese können komplex sein, wenn es um kaskadierende Fremdschlüssel und indizierte Ansichten geht. Wenn alles andere fehlschlägt, stellen Sie möglicherweise fest, dass ein Ausführungsplan ohne Sortierung (um Zeilen in der Reihenfolge der Indexschlüssel zu ordnen) das beste Ergebnis liefert. Es gibt keine dokumentierte Möglichkeit, Pläne ohne diese Art zu generieren, aber Sie könnten versuchen, den Plan zu erzwingen, der mit dem undokumentierten Ablaufverfolgungsflag 8795 aktiviert wurde .

Sie können auch einen geeigneten MAX_GRANT_PERCENT Abfragehinweis ausprobieren oder einfach genug Speicher installieren (oder SQL Server zur Verfügung stellen), um Ihre Arbeitslast zu bewältigen.

Paul White 9
quelle
6

Beide Pläne haben Optimized="false"für die inneren Verbindungen und haben die gleiche Form. Ich verstehe aus dem Blog-Beitrag, auf den verwiesen wird, dass das Problem mit der Speichergewährung, bei dem die Schätzung der äußeren Tabelle für die Gewährung verwendet wird, nur mit gilt Optimized="true". Außerdem haben beide Pläne eine Zeilenanzahl von 1 für die äußere Tabelle der verschachtelten Schleife (Tabellenspool mit gelöschter Kurs-ID).

Ich vermute klassisches Parameter-Sniffing. Die geschätzte Zeilenzahl des Operators für verschachtelte Schleifen vor dem Clustered-Index-Löschen von CoursePrerequisiteAssignment (kaskadiertes Löschen?) Beträgt im oberen Plan etwa 25 Millionen gegenüber 240 im unteren. Das ist wahrscheinlich das, was den gewünschten Speicher antreibt.

OPTION(RECOMPILE)könnte die einfachste und beste Lösung sein. Unabhängig davon kann die Indexoptimierung dazu beitragen, das vollständige Scannen der CoursePrerequisiteAssignmentTabelle bei jedem Löschen eines Kurses zu vermeiden .

Dan Guzman
quelle
Ich bekomme das gleiche Problem, wenn ich es ohne einen Parameter ausführe, ex: id = 1234, außerdem gibt es Indizes für die CoursePrerequisiteAssignment
sebeid
Wie hoch ist die geschätzte Zeilenanzahl der verschachtelten Schleife mit dem Literal? Welcher Index für CoursePrerequisiteAssignment enthält CourseID als erste Spalte?
Dan Guzman
Es wurden 16000 Zeilen zurückgegeben, ja, es gibt einen Index mit der ersten Spalte von CourseID. Ich habe nur verwirrt, warum die geschätzte Zeilenanzahl für eine 25 Millionen Zeilen beträgt. Dies ist die gesamte Tabelle. Ist das ein Statistikproblem?
Sebeid
Die geschätzte Zeilenanzahl mit einem Parameter oder Literal basiert auf dem Statistikhistogramm. Wenn diese Statistiken veraltet sind, ist die geschätzte Zeilenanzahl möglicherweise falsch und wirkt sich auf die Planauswahl aus. Können Sie Ihrer Frage DDL für die Tabellen, Einschränkungen und Indizes hinzufügen? Ich bin überrascht, dass der erste Plan (mit der niedrigen Schätzung) einen Scan von CoursePrerequisiteAssignment durchführt, anstatt eine Verwendung der CourseID zu suchen.
Dan Guzman