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

Optimierungsphasen und verpasste Chancen

Es gibt zwei sich ergänzende Fähigkeiten, die bei der Abfrageoptimierung sehr nützlich sind. Eine davon ist die Fähigkeit, Ausführungspläne zu lesen und zu interpretieren. Die zweite besteht darin, ein wenig darüber zu wissen, wie der Abfrageoptimierer funktioniert, um SQL-Text in einen Ausführungsplan zu übersetzen. Die Kombination dieser beiden Dinge kann uns helfen, Zeiten zu erkennen, in denen eine erwartete Optimierung nicht angewendet wurde, was zu einem Ausführungsplan führt, der nicht so effizient ist, wie er sein könnte. Der Mangel an Dokumentation darüber, welche Optimierungen SQL Server anwenden kann (und unter welchen Umständen), bedeutet jedoch, dass vieles davon auf Erfahrung zurückzuführen ist.

Ein Beispiel

Die Beispielabfrage für diesen Artikel basiert auf einer Frage, die Fabiano Amorim, MVP von SQL Server, vor einigen Monaten gestellt hat, basierend auf einem realen Problem, auf das er gestoßen ist. Das Schema und die Testabfrage unten sind eine Vereinfachung der realen Situation, behalten aber alle wichtigen Funktionen bei.

CREATE TABLE dbo.T1 (pk integer PRIMARY KEY, c1 integer NOT NULL);
CREATE TABLE dbo.T2 (pk integer PRIMARY KEY, c1 integer NOT NULL);
CREATE TABLE dbo.T3 (pk integer PRIMARY KEY, c1 integer NOT NULL);
GO
CREATE INDEX nc1 ON dbo.T1 (c1);
CREATE INDEX nc1 ON dbo.T2 (c1);
CREATE INDEX nc1 ON dbo.T3 (c1);
GO
CREATE VIEW dbo.V1
AS
    SELECT c1 FROM dbo.T1
    UNION ALL
    SELECT c1 FROM dbo.T2
    UNION ALL
    SELECT c1 FROM dbo.T3;
GO
-- The test query
SELECT MAX(c1)
FROM dbo.V1;

Test 1 – 10.000 Zeilen, SQL Server 2005+

Die spezifischen Tabellendaten spielen für diese Tests keine Rolle. Die folgenden Abfragen laden einfach 10.000 Zeilen aus einer Zahlentabelle in jede der drei Testtabellen:

INSERT dbo.T1 (pk, c1)
SELECT n, n
FROM dbo.Numbers AS N
WHERE n BETWEEN 1 AND 10000;
 
INSERT dbo.T2 (pk, c1)
SELECT pk, c1 FROM dbo.T1;
 
INSERT dbo.T3 (pk, c1)
SELECT pk, c1 FROM dbo.T1;


Mit den geladenen Daten lautet der für die Testabfrage erstellte Ausführungsplan:

SELECT MAX(c1) FROM dbo.V1;

Dieser Ausführungsplan ist eine ziemlich direkte Implementierung der logischen SQL-Abfrage (nachdem die Ansichtsreferenz V1 erweitert wurde). Der Optimierer sieht die Abfrage nach der Ansichtserweiterung fast so, als ob die Abfrage vollständig ausgeschrieben worden wäre:

SELECT MAX(c1)
FROM
(
    SELECT c1 FROM dbo.T1
    UNION ALL
    SELECT c1 FROM dbo.T2
    UNION ALL
    SELECT c1 FROM dbo.T3
) AS V1;

Beim Vergleich des erweiterten Textes mit dem Ausführungsplan wird die Direktheit der Implementierung des Abfrageoptimierers deutlich. Es gibt einen Index-Scan für jeden Lesevorgang der Basistabellen, einen Concatenation-Operator zur Implementierung von UNION ALL und ein Stream-Aggregat für das letzte MAX aggregieren.

Die Eigenschaften des Ausführungsplans zeigen, dass die kostenbasierte Optimierung gestartet wurde (Optimierungsstufe ist FULL ), sondern dass es vorzeitig beendet wurde, weil ein Plan gefunden wurde, der „gut genug“ war. Die geschätzten Kosten des ausgewählten Tarifs betragen 0,1016240 magische Optimierungseinheiten.

Test 2 – 50.000 Zeilen, SQL Server 2008 und 2008 R2

Führen Sie das folgende Skript aus, um die Testumgebung auf die Ausführung mit 50.000 Zeilen zurückzusetzen:

TRUNCATE TABLE dbo.T1;
TRUNCATE TABLE dbo.T2;
TRUNCATE TABLE dbo.T3;
 
INSERT dbo.T1 (pk, c1)
SELECT n, n
FROM dbo.Numbers AS N
WHERE n BETWEEN 1 AND 50000;
 
INSERT dbo.T2 (pk, c1)
SELECT pk, c1 FROM dbo.T1;
 
INSERT dbo.T3 (pk, c1)
SELECT pk, c1 FROM dbo.T1;
 
SELECT MAX(c1) 
FROM dbo.V1;

Der Ausführungsplan für diesen Test hängt von der Version von SQL Server ab, die Sie ausführen. In SQL Server 2008 und 2008 R2 erhalten wir den folgenden Plan:

Die Planeigenschaften zeigen, dass die kostenbasierte Optimierung nach wie vor aus demselben Grund vorzeitig endete. Die geschätzten Kosten sind mit 0,41375 höher als zuvor Einheiten, aber das ist aufgrund der höheren Kardinalität der Basistabellen zu erwarten.

Test 3 – 50.000 Zeilen, SQL Server 2005 und 2012

Dieselbe Abfrage, die 2005 oder 2012 ausgeführt wird, erzeugt einen anderen Ausführungsplan:

Die Optimierung wurde erneut vorzeitig beendet, aber die geschätzten Plankosten für 50.000 Zeilen pro Basistabelle sind auf 0,0098585 gesunken (ab 0,41375 auf SQL Server 2008 und 2008 R2).

Erklärung

Wie Sie vielleicht wissen, unterteilt der SQL Server-Abfrageoptimierer den Optimierungsaufwand in mehrere Phasen, wobei in späteren Phasen weitere Optimierungstechniken hinzugefügt werden und mehr Zeit zur Verfügung steht. Die Optimierungsstufen sind:

  • Trivialplan
  • Kostenbasierte Optimierung
    • Transaktionsverarbeitung (Suche 0)
    • Schnellplan (Suche 1)
    • Quick Plan mit aktivierter Parallelität
    • Vollständige Optimierung (Suche 2)

Keiner der hier durchgeführten Tests qualifiziert sich für einen trivialen Plan, da das Aggregat und die Vereinigungen mehrere Implementierungsmöglichkeiten haben, was eine kostenbasierte Entscheidung erfordert.

Transaktionsverarbeitung

Die Transaktionsverarbeitung (TP)-Phase erfordert, dass eine Abfrage mindestens drei Tabellenreferenzen enthält, andernfalls überspringt die kostenbasierte Optimierung diese Phase und fährt direkt mit Quick Plan fort. Die TP-Phase zielt auf die für OLTP-Workloads typischen kostengünstigen Navigationsabfragen ab. Es versucht eine begrenzte Anzahl von Optimierungstechniken und ist darauf beschränkt, Pläne mit Nested-Loop-Joins zu finden (es sei denn, ein Hash-Join ist erforderlich, um einen gültigen Plan zu generieren).

In mancher Hinsicht überrascht, dass sich die Testabfrage für eine Phase qualifiziert, die darauf abzielt, OLTP-Pläne zu finden. Obwohl die Abfrage die erforderlichen drei Tabellenreferenzen enthält, enthält sie keine Joins. Die Drei-Tabellen-Anforderung ist nur eine Heuristik, also werde ich nicht weiter darauf eingehen.

Welche Optimierungsphasen wurden ausgeführt?

Es gibt eine Reihe von Methoden, von denen die dokumentierte darin besteht, den Inhalt von sys.dm_exec_query_optimizer_info vor und nach der Kompilierung zu vergleichen. Das ist in Ordnung, aber es zeichnet instanzweite Informationen auf, also müssen Sie darauf achten, dass Ihre Abfrage die einzige Abfragekompilierung ist, die zwischen Snapshots stattfindet.

Eine undokumentierte (aber einigermaßen bekannte) Alternative, die auf allen derzeit unterstützten Versionen von SQL Server funktioniert, besteht darin, die Trace-Flags 8675 und 3604 zu aktivieren, während die Abfrage kompiliert wird.

Test 1

Dieser Test erzeugt eine Ausgabe des Ablaufverfolgungsflags 8675 ähnlich der folgenden:

Die geschätzten Kosten von 0,101624 nach der TP-Phase sind niedrig genug, dass der Optimierer nicht weiter nach billigeren Plänen sucht. Der einfache Plan, den wir am Ende erhalten, ist angesichts der relativ niedrigen Kardinalität der Basistabellen ziemlich vernünftig, auch wenn er nicht wirklich optimal ist.

Test 2

Bei 50.000 Zeilen in jeder Basistabelle zeigt das Trace-Flag andere Informationen:

Diesmal betragen die geschätzten Kosten nach der TP-Phase 0,428735 (mehr Zeilen =höhere Kosten). Dies reicht aus, um den Optimierer in die Quick-Plan-Phase zu bringen. Da mehr Optimierungstechniken verfügbar sind, findet diese Phase einen Plan mit Kosten von 0,41375 . Dies stellt keine große Verbesserung gegenüber dem Test 1-Plan dar, aber es ist niedriger als der Standardkostenschwellenwert für Parallelität und nicht genug, um in die vollständige Optimierung einzutreten, sodass die Optimierung erneut vorzeitig endet.

Test 3

Für die Ausführung von SQL Server 2005 und 2012 lautet die Ausgabe des Ablaufverfolgungsflags:

Es gibt geringfügige Unterschiede in der Anzahl der ausgeführten Aufgaben zwischen den Versionen, aber der wichtige Unterschied besteht darin, dass die Quick Plan-Phase auf SQL Server 2005 und 2012 einen Plan findet, der nur 0,0098543 kostet Einheiten. Dies ist der Plan, der Top-Operatoren anstelle der drei Stream-Aggregate unterhalb des Concatenation-Operators enthält, der in den SQL Server 2008- und 2008 R2-Plänen zu sehen ist.

Fehler und undokumentierte Korrekturen

SQL Server 2008 und 2008 R2 enthalten einen Regressionsfehler (im Vergleich zu 2005), der unter dem Trace-Flag 4199 behoben, aber soweit ich das beurteilen kann, nicht dokumentiert wurde. Es gibt eine Dokumentation für TF 4199, die Fixes auflistet, die unter separaten Trace-Flags verfügbar gemacht wurden, bevor sie von 4199 abgedeckt wurden, aber wie dieser Knowledge Base-Artikel sagt:

Dieses eine Ablaufverfolgungsflag kann verwendet werden, um alle Korrekturen zu aktivieren, die zuvor für den Abfrageprozessor unter vielen Ablaufverfolgungsflags vorgenommen wurden. Außerdem werden alle zukünftigen Fehlerbehebungen des Abfrageprozessors mithilfe dieses Ablaufverfolgungsflags gesteuert.

Der Fehler in diesem Fall ist eine dieser „zukünftigen Fehlerbehebungen für den Abfrageprozessor“. Eine bestimmte Optimierungsregel, ScalarGbAggToTop , wird nicht auf die neuen Aggregate angewendet, die im Plan von Test 2 zu sehen sind. Wenn das Trace-Flag 4199 auf geeigneten Builds von SQL Server 2008 und 2008 R2 aktiviert ist, wird der Fehler behoben und der optimale Plan aus Test 3 wird abgerufen:

-- Trace flag 4199 required for 2008 and 2008 R2
SELECT MAX(c1) 
FROM dbo.V1
OPTION (QUERYTRACEON 4199);

Schlussfolgerung

Sobald Sie wissen, dass der Optimierer einen Skalar MIN umwandeln kann oder MAX aggregieren zu einem TOP (1) Bei einem bestellten Stream erscheint der in Test 2 gezeigte Plan seltsam. Die skalaren Aggregate über einem Index-Scan (der auf Anfrage für Ordnung sorgen kann) fallen als versäumte Optimierung auf, die normalerweise angewendet würde.

Das ist der Punkt, den ich in der Einführung angesprochen habe:Sobald Sie ein Gefühl dafür bekommen, was der Optimierer tun kann, kann er Ihnen helfen, Fälle zu erkennen, in denen etwas schief gelaufen ist.

Die Antwort wird nicht immer darin bestehen, das Trace-Flag 4199 zu aktivieren, da Sie möglicherweise auf Probleme stoßen, die noch nicht behoben wurden. Möglicherweise möchten Sie auch nicht, dass die anderen vom Trace-Flag abgedeckten QP-Fixes in einem bestimmten Fall angewendet werden – Optimierer-Fixes machen die Dinge nicht immer besser. Wenn dies der Fall wäre, wäre es nicht erforderlich, sich mit diesem Flag vor unglücklichen Planregressionen zu schützen.

In anderen Fällen könnte die Lösung darin bestehen, die SQL-Abfrage mit einer anderen Syntax zu formulieren, die Abfrage in optimierterfreundlichere Teile aufzuteilen oder etwas ganz anderes. Was auch immer die Antwort sein mag, es zahlt sich immer noch aus, etwas über die Interna des Optimierers zu wissen, damit Sie erkennen können, dass es überhaupt ein Problem gab :)