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

Optimierungsschwellenwerte – Gruppieren und Aggregieren von Daten, Teil 4

Dieser Artikel ist der vierte in einer Reihe über Optimierungsschwellenwerte. Die Reihe behandelt das Gruppieren und Aggregieren von Daten und erläutert die verschiedenen Algorithmen, die SQL Server verwenden kann, sowie das Kostenmodell, das ihm bei der Auswahl zwischen den Algorithmen hilft. In diesem Artikel konzentriere ich mich auf Überlegungen zur Parallelität. Ich behandele die verschiedenen Parallelitätsstrategien, die SQL Server verwenden kann, die Schwellenwerte für die Auswahl zwischen einem seriellen und einem parallelen Plan und die Kostenlogik, die SQL Server anwendet, indem ich ein Konzept namens Parallelitätsgrad für die Kostenberechnung verwende (DOP für Kalkulation).

Ich werde in meinen Beispielen weiterhin die Tabelle dbo.Orders in der PerformanceV3-Beispieldatenbank verwenden. Führen Sie vor dem Ausführen der Beispiele in diesem Artikel den folgenden Code aus, um einige nicht benötigte Indizes zu löschen:

INDEX DROP IF EXISTS idx_nc_sid_od_cid ON dbo.Orders;INDEX DROP IF EXISTS idx_unc_od_oid_i_cid_eid ON dbo.Orders;

Die einzigen beiden Indizes, die in dieser Tabelle verbleiben sollten, sind idx_cl_od (geclustert mit orderdate als Schlüssel) und PK_Orders (nicht geclustert mit orderid als Schlüssel).

Parallelitätsstrategien

Neben der Wahl zwischen verschiedenen Gruppierungs- und Aggregationsstrategien (vorbestelltes Stream Aggregate, Sort + Stream Aggregate, Hash Aggregate) muss SQL Server auch entscheiden, ob es sich um einen seriellen oder einen parallelen Plan handelt. Tatsächlich kann es zwischen mehreren verschiedenen Parallelisierungsstrategien wählen. SQL Server verwendet Kostenrechnungslogik, die zu Optimierungsschwellenwerten führt, die unter verschiedenen Bedingungen dazu führen, dass eine Strategie den anderen vorgezogen wird. Wir haben die Kostenlogik, die SQL Server in seriellen Plänen verwendet, bereits in den vorherigen Teilen der Reihe ausführlich besprochen. In diesem Abschnitt stelle ich eine Reihe von Parallelitätsstrategien vor, die SQL Server für die Behandlung von Gruppierung und Aggregation verwenden kann. Ich gehe zunächst nicht auf die Details der Kalkulationslogik ein, sondern beschreibe nur die zur Verfügung stehenden Möglichkeiten. Später in diesem Artikel werde ich erklären, wie die Kalkulationsformeln funktionieren, und einen wichtigen Faktor in diesen Formeln namens DOP für die Kalkulation.

Wie Sie später erfahren werden, berücksichtigt SQL Server die Anzahl der logischen CPUs in der Maschine in seinen Kostenformeln für parallele Pläne. In meinen Beispielen gehe ich, sofern ich nichts anderes sage, davon aus, dass das Zielsystem 8 logische CPUs hat. Wenn Sie die von mir bereitgestellten Beispiele ausprobieren möchten, um dieselben Pläne und Kostenwerte wie ich zu erhalten, müssen Sie den Code auch auf einem Computer mit 8 logischen CPUs ausführen. Wenn Ihre Maschine zufällig eine andere Anzahl von CPUs hat, können Sie aus Kostengründen eine Maschine mit 8 CPUs wie folgt emulieren:

DBCC OPTIMIZER_WHATIF(CPUs, 8);

Obwohl dieses Tool nicht offiziell dokumentiert und unterstützt wird, ist es für Forschungs- und Lernzwecke sehr praktisch.

Die Orders-Tabelle in unserer Beispieldatenbank hat 1.000.000 Zeilen mit Order-IDs im Bereich von 1 bis 1.000.000. Um drei verschiedene Parallelitätsstrategien für die Gruppierung und Aggregation zu demonstrieren, werde ich Bestellungen filtern, bei denen die Bestell-ID größer oder gleich 300001 (700.000 Übereinstimmungen) ist, und die Daten auf drei verschiedene Arten gruppieren (nach custid [20.000 Gruppen vor dem Filtern], von empid [500 Gruppen] und von shipperid [5 Gruppen]) und berechnen Sie die Anzahl der Bestellungen pro Gruppe.

Verwenden Sie den folgenden Code, um Indizes zur Unterstützung der gruppierten Abfragen zu erstellen:

CREATE INDEX idx_oid_i_eid ON dbo.Orders(orderid) INCLUDE(empid);CREATE INDEX idx_oid_i_sid ON dbo.Orders(orderid) INCLUDE(shipperid);CREATE INDEX idx_oid_i_cid ON dbo.Orders(orderid) INCLUDE(custid); 

Die folgenden Abfragen implementieren die oben genannte Filterung und Gruppierung:

-- Abfrage 1:Serial SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custidOPTION(MAXDOP 1); -- Abfrage 2:Parallel, nicht lokal/global SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custid; - Abfrage 3:Lokales paralleles globales paralleles SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Abfrage 4:Lokale parallele globale Seriennummer SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperid;

Beachten Sie, dass Abfrage 1 und Abfrage 2 identisch sind (beide gruppieren nach custid), nur erstere erzwingt einen seriellen Plan und letztere erhält einen parallelen Plan auf einer Maschine mit 8 CPUs. Ich verwende diese beiden Beispiele, um die seriellen und parallelen Strategien für dieselbe Abfrage zu vergleichen.

Abbildung 1 zeigt die geschätzten Pläne für alle vier Abfragen:

Abbildung 1:Parallelitätsstrategien

Machen Sie sich vorerst keine Gedanken über die in der Abbildung gezeigten Kostenwerte und die Erwähnung des Begriffs DOP für Kosten. Zu denen komme ich später. Konzentrieren Sie sich zunächst darauf, die Strategien und die Unterschiede zwischen ihnen zu verstehen.

Die im Serienplan für Abfrage 1 verwendete Strategie sollte Ihnen aus den vorherigen Teilen der Serie bekannt sein. Der Plan filtert die relevanten Aufträge mithilfe einer Suche im zuvor erstellten Deckungsindex. Mit der geschätzten Anzahl der zu gruppierenden und zu aggregierenden Zeilen bevorzugt der Optimierer dann die Strategie Hash Aggregate gegenüber der Strategie Sort + Stream Aggregate.

Der Plan für Abfrage 2 verwendet eine einfache Parallelitätsstrategie, die nur einen Aggregatoperator verwendet. Ein paralleler Index-Seek-Operator verteilt Zeilenpakete auf die verschiedenen Threads in einer Round-Robin-Weise. Jedes Zeilenpaket kann mehrere eindeutige Kunden-IDs enthalten. Damit ein einzelner Aggregatoperator die korrekten endgültigen Gruppenzahlen berechnen kann, müssen alle Zeilen, die zu derselben Gruppe gehören, von demselben Thread verarbeitet werden. Aus diesem Grund wird ein Austauschoperator für Parallelität (Repartition Streams) verwendet, um die Streams nach dem Gruppierungssatz (custid) neu zu partitionieren. Schließlich wird ein Parallelism (Streams sammeln)-Austauschoperator verwendet, um die Streams von den mehreren Threads in einem einzigen Stream von Ergebniszeilen zu sammeln.

Die Pläne für Abfrage 3 und Abfrage 4 verwenden eine komplexere Parallelitätsstrategie. Die Pläne beginnen ähnlich wie der Plan für Abfrage 2, wo ein paralleler Indexsuchoperator Zeilenpakete an verschiedene Threads verteilt. Dann erfolgt die Aggregationsarbeit in zwei Schritten:Ein Aggregatoperator gruppiert und aggregiert die Zeilen des aktuellen Threads lokal (beachten Sie das Ergebnismember partialagg1004), und ein zweiter Aggregatoperator gruppiert und aggregiert die Ergebnisse der lokalen Aggregate global (beachten Sie das globalagg1005 Ergebnismitglied). Jeder der beiden Aggregationsschritte – lokal und global – kann jeden der Aggregatalgorithmen verwenden, die ich zuvor in der Serie beschrieben habe. Beide Pläne für Abfrage 3 und Abfrage 4 beginnen mit einem lokalen Hash-Aggregat und fahren mit einem globalen Sort + Stream-Aggregat fort. Der Unterschied zwischen den beiden besteht darin, dass Ersteres in beiden Schritten Parallelität verwendet (daher wird ein Repartition Streams-Austausch zwischen den beiden und ein Gather Streams-Austausch nach dem globalen Aggregat verwendet), und Letzteres behandelt das lokale Aggregat in einer parallelen Zone und das globale aggregieren in einer seriellen Zone (daher wird ein Gather Streams-Austausch zwischen den beiden verwendet).

Wenn Sie Ihre Recherchen zur Abfrageoptimierung im Allgemeinen und zur Parallelität im Besonderen durchführen, ist es gut, mit Tools vertraut zu sein, mit denen Sie verschiedene Optimierungsaspekte steuern können, um ihre Auswirkungen zu sehen. Sie wissen bereits, wie Sie einen seriellen Plan erzwingen (mit einem MAXDOP 1-Hinweis) und wie Sie eine Umgebung emulieren, die aus Kostengründen über eine bestimmte Anzahl logischer CPUs verfügt (DBCC OPTIMIZER_WHATIF, mit der Option CPUs). Ein weiteres praktisches Tool ist der Abfragehinweis ENABLE_PARALLEL_PLAN_PREFERENCE (eingeführt in SQL Server 2016 SP1 CU2), der die Parallelität maximiert. Was ich damit meine ist, dass, wenn ein paralleler Plan für die Abfrage unterstützt wird, Parallelität in allen Teilen des Plans bevorzugt wird, die parallel behandelt werden können, als ob es kostenlos wäre. Beachten Sie beispielsweise in Abbildung 1, dass der Plan für Abfrage 4 standardmäßig das lokale Aggregat in einer seriellen Zone und das globale Aggregat in einer parallelen Zone verarbeitet. Hier ist die gleiche Abfrage, nur dieses Mal mit dem angewendeten Abfragehinweis ENABLE_PARALLEL_PLAN_PREFERENCE (wir nennen sie Abfrage 5):

SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperidOPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

Der Plan für Abfrage 5 ist in Abbildung 2 dargestellt:

Abbildung 2:Maximierung der Parallelität

Beachten Sie, dass diesmal sowohl die lokalen als auch die globalen Aggregate in parallelen Zonen behandelt werden.

Auswahl serieller/paralleler Pläne

Denken Sie daran, dass SQL Server während der Abfrageoptimierung mehrere Kandidatenpläne erstellt und den mit den niedrigsten Kosten unter den erstellten auswählt. Der Begriff Kosten ist ein bisschen irreführend, da der Plankandidat mit den niedrigsten Kosten den Schätzungen zufolge derjenige mit der niedrigsten Laufzeit sein soll, nicht der mit dem geringsten Ressourcenverbrauch insgesamt. Zwischen einem seriellen Kandidatenplan und einem parallelen Plan, der für dieselbe Abfrage erstellt wurde, verbraucht der parallele Plan beispielsweise wahrscheinlich mehr Ressourcen, da er Austauschoperatoren verwenden muss, die die Threads synchronisieren (Streams verteilen, neu partitionieren und sammeln). Damit der parallele Plan jedoch weniger Zeit zum Ausführen benötigt als der serielle Plan, müssen die Einsparungen, die durch die Arbeit mit mehreren Threads erzielt werden, die zusätzliche Arbeit der Austauschbetreiber aufwiegen. Und dies muss durch die Kalkulationsformeln widergespiegelt werden, die SQL Server verwendet, wenn es um Parallelität geht. Keine einfache Aufgabe, um sie genau auszuführen!

Zusätzlich zu den Kosten des parallelen Plans, die niedriger sein müssen als die Kosten des seriellen Plans, um bevorzugt zu werden, müssen die Kosten der seriellen Planalternative größer oder gleich dem Kostenschwellenwert für Parallelität sein . Dies ist eine Serverkonfigurationsoption, die standardmäßig auf 5 gesetzt ist und verhindert, dass Abfragen mit relativ geringen Kosten parallel verarbeitet werden. Der Gedanke hier ist, dass ein System mit einer großen Anzahl kleiner Abfragen insgesamt mehr von der Verwendung serieller Pläne profitieren würde, anstatt viele Ressourcen für die Synchronisierung von Threads zu verschwenden. Sie können immer noch mehrere Abfragen mit seriellen Plänen gleichzeitig ausführen und so die Multi-CPU-Ressourcen der Maschine effizient nutzen. Tatsächlich erhöhen viele SQL Server-Experten den Kostenschwellenwert für Parallelität gerne von seinem Standardwert von 5 auf einen höheren Wert. Ein System, auf dem eine relativ kleine Anzahl großer Abfragen gleichzeitig ausgeführt wird, würde viel mehr von der Verwendung paralleler Pläne profitieren.

Um es noch einmal zusammenzufassen:Damit SQL Server einen parallelen Plan der seriellen Alternative vorzieht, müssen die Kosten des seriellen Plans mindestens dem Kostenschwellenwert für Parallelität entsprechen, und die Kosten des parallelen Plans müssen niedriger sein als die Kosten des seriellen Plans (was potenziell bedeutet geringere Laufzeit).

Bevor ich auf die Details der eigentlichen Kalkulationsformeln eingehe, werde ich anhand von Beispielen verschiedene Szenarien veranschaulichen, bei denen zwischen seriellen und parallelen Plänen gewählt wird. Stellen Sie sicher, dass Ihr System von 8 logischen CPUs ausgeht, um ähnliche Abfragekosten wie bei mir zu erhalten, wenn Sie die Beispiele ausprobieren möchten.

Betrachten Sie die folgenden Abfragen (wir nennen sie Abfrage 6 und Abfrage 7):

-- Abfrage 6:Serial SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empid; -- Abfrage 7:Erzwungenes paralleles SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empidOPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

Die Pläne für diese Abfragen sind in Abbildung 3 dargestellt.

Abbildung 3:Serienkosten

Hier sind die [erzwungenen] parallelen Plankosten niedriger als die seriellen Plankosten; Die Kosten für den seriellen Plan sind jedoch niedriger als der Standardkostenschwellenwert für Parallelität von 5, daher wählte SQL Server standardmäßig den seriellen Plan.

Betrachten Sie die folgenden Abfragen (wir nennen sie Abfrage 8 und Abfrage 9):

-- Abfrage 8:Parallel SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Abfrage 9:Erzwungenes serielles SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empidOPTION(MAXDOP 1);

Die Pläne für diese Abfragen sind in Abbildung 4 dargestellt.

Abbildung 4:Serienkosten>=Kostenschwelle für Parallelität, Parallelkosten

Hier sind die [erzwungenen] Kosten des seriellen Plans größer oder gleich dem Kostenschwellenwert für Parallelität, und die Kosten des parallelen Plans sind niedriger als die Kosten des seriellen Plans, daher wählte SQL Server standardmäßig den parallelen Plan.

Betrachten Sie die folgenden Abfragen (wir nennen sie Abfrage 10 und Abfrage 11):

-- Abfrage 10:Serial SELECT *FROM dbo.OrdersWHERE orderid>=100000; -- Abfrage 11:Erzwungenes paralleles SELECT *FROM dbo.OrdersWHERE orderid>=100000OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

Die Pläne für diese Abfragen sind in Abbildung 5 dargestellt.

Abbildung 5:Serienkosten>=Kostenschwelle für Parallelität, Parallelkosten>=Serienkosten

Hier sind die seriellen Plankosten größer oder gleich dem Kostenschwellenwert für Parallelität; Die Kosten für den seriellen Plan sind jedoch niedriger als die Kosten für den [erzwungenen] parallelen Plan, daher wählte SQL Server standardmäßig den seriellen Plan.

Es gibt noch etwas, das Sie wissen müssen, wenn Sie versuchen, die Parallelität mit dem ENABLE_PARALLEL_PLAN_PREFERENCE-Hinweis zu maximieren. Damit SQL Server überhaupt in der Lage ist, einen parallelen Plan zu verwenden, muss es einen Parallelitäts-Enabler wie ein Restprädikat, eine Sortierung, ein Aggregat usw. geben. Ein Plan, der nur einen Index-Scan oder eine Index-Suche ohne ein Restprädikat und ohne einen anderen Parallelitäts-Enabler anwendet, wird mit einem seriellen Plan verarbeitet. Betrachten Sie die folgenden Abfragen als Beispiel (wir nennen sie Abfrage 12 und Abfrage 13):

-- Abfrage 12 SELECT *FROM dbo.OrdersOPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE')); -- Abfrage 13 SELECT *FROM dbo.OrdersWHERE orderid>=100000OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

Die Pläne für diese Abfragen sind in Abbildung 6 dargestellt.

Abbildung 6:Aktivierungsprogramm für Parallelität

Abfrage 12 erhält trotz des Hinweises einen seriellen Plan, da es keinen Parallelitäts-Enabler gibt. Abfrage 13 erhält einen parallelen Plan, da ein Restprädikat beteiligt ist.

Berechnen und Testen von DOP für die Kalkulation

Microsoft musste die Kalkulationsformeln kalibrieren, um zu versuchen, dass niedrigere parallele Plankosten als die seriellen Plankosten eine geringere Laufzeit widerspiegeln und umgekehrt. Eine mögliche Idee war, die CPU-Kosten des seriellen Operators zu nehmen und sie einfach durch die Anzahl der logischen CPUs in der Maschine zu dividieren, um die CPU-Kosten des parallelen Operators zu erhalten. Die logische Anzahl der CPUs in der Maschine ist der Hauptfaktor, der den Grad der Parallelität der Abfrage bestimmt, oder kurz DOP (die Anzahl der Threads, die in einer parallelen Zone im Plan verwendet werden können). Der vereinfachende Gedanke hier ist, dass, wenn ein Operator T Zeiteinheiten benötigt, um ihn abzuschließen, wenn er einen Thread verwendet, und der Grad der Parallelität der Abfrage D ist, würde der Operator T/D-Zeit benötigen, um ihn abzuschließen, wenn er D-Threads verwendet. In der Praxis liegen die Dinge nicht so einfach. Beispielsweise laufen in der Regel mehrere Abfragen gleichzeitig und nicht nur eine. In diesem Fall erhält eine einzelne Abfrage nicht alle CPU-Ressourcen des Computers. Also kam Microsoft auf die Idee des Parallelitätsgrads für die Kostenrechnung (DOP für Kalkulation, kurz). Dieses Maß ist in der Regel niedriger als die Anzahl der logischen CPUs in der Maschine und ist der Faktor, durch den die CPU-Kosten des seriellen Operators dividiert werden, um die CPU-Kosten des parallelen Operators zu berechnen.

Normalerweise wird DOP für die Kalkulation als die Anzahl der logischen CPUs geteilt durch 2 berechnet, wobei eine ganzzahlige Division verwendet wird. Es gibt jedoch Ausnahmen. Wenn die Anzahl der CPUs 2 oder 3 beträgt, wird der DOP für die Kostenrechnung auf 2 gesetzt. Bei 4 oder mehr CPUs wird der DOP für die Kostenrechnung auf #CPUs / 2 gesetzt, wiederum unter Verwendung einer ganzzahligen Division. Dies gilt bis zu einem bestimmten Maximum, das von der für die Maschine verfügbaren Speichermenge abhängt. Bei einer Maschine mit bis zu 4.096 MB Speicher beträgt die maximale DOP für die Kalkulation 8; mit mehr als 4.096 MB beträgt die maximale DOP für die Kosten 32.

Um diese Logik zu testen, wissen Sie bereits, wie Sie eine gewünschte Anzahl logischer CPUs mit DBCC OPTIMIZER_WHATIF mit der CPUs-Option wie folgt emulieren:

DBCC OPTIMIZER_WHATIF(CPUs, 8);

Unter Verwendung desselben Befehls mit der Option MemoryMBs können Sie eine gewünschte Speichermenge in MB emulieren, etwa so:

DBCC OPTIMIZER_WHATIF(MemoryMBs, 16384);

Verwenden Sie den folgenden Code, um den vorhandenen Status der emulierten Optionen zu überprüfen:

DBCC TRACEON(3604); DBCC OPTIMIZER_WHATIF (Status); DBCC TRACEOFF(3604);

Verwenden Sie den folgenden Code, um alle Optionen zurückzusetzen:

DBCC OPTIMIZER_WHATIF(ResetAll);

Hier ist eine T-SQL-Abfrage, die Sie verwenden können, um den DOP für die Kalkulation basierend auf einer eingegebenen Anzahl logischer CPUs und der Menge an Arbeitsspeicher zu berechnen:

DECLARE @NumCPUs AS INT =8, @MemoryMBs AS INT =16384; FALL AUSWÄHLEN WENN @NumCPUs =1 DANN 1 WENN @NumCPUs <=3 DANN 2 WENN @NumCPUs>=4 DANN (SELECT MIN(n) FROM ( VALUES(@NumCPUs / 2), (MaxDOP4C ) ) AS D2(n)) END AS DOP4CFROM ( VALUES( CASE WHEN @MemoryMBs <=4096 THEN 8 ELSE 32 END ) ) AS D1(MaxDOP4C);

Mit den angegebenen Eingabewerten gibt diese Abfrage 4 zurück.

Tabelle 1 zeigt die DOP für die Kosten, die Sie basierend auf der logischen Anzahl von CPUs und der Menge an Arbeitsspeicher in Ihrem Computer erhalten.

#CPUs DOP für Kosten bei MemoryMBs <=4096 DOP für Kosten bei MemoryMBs> 4096
1 1 1
2-5 2 2
6-7 3 3
8-9 4 4
10-11 5 5
12-13 6 6
14-15 7 7
16-17 8 8
18-19 8 9
20-21 8 10
22-23 8 11
24-25 8 12
26-27 8 13
28-29 8 14
30-31 8 15
32-33 8 16
34-35 8 17
36-37 8 18
38-39 8 19
40-41 8 20
42-43 8 21
44-45 8 22
46-47 8 23
48-49 8 24
50-51 8 25
52-53 8 26
54-55 8 27
56-57 8 28
58-59 8 29
60-61 8 30
62-63 8 31
>=64 8 32

Tabelle 1:DOP für die Kalkulation

Sehen wir uns als Beispiel die zuvor gezeigten Abfragen 1 und 2 noch einmal an:

-- Abfrage 1:Erzwungenes serielles SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custidOPTION(MAXDOP 1); -- Abfrage 2:Natürlich parallel SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custid;

Die Pläne für diese Abfragen sind in Abbildung 7 dargestellt.

Abbildung 7:DOP für Kosten

Abfrage 1 erzwingt einen seriellen Plan, während Abfrage 2 in meiner Umgebung einen parallelen Plan erhält (emuliert 8 logische CPUs und 16.384 MB Arbeitsspeicher). Dies bedeutet, dass die DOP für die Kostenberechnung in meiner Umgebung 4 beträgt. Wie bereits erwähnt, werden die CPU-Kosten eines parallelen Operators berechnet als die CPU-Kosten des seriellen Operators geteilt durch die DOP für die Kosten. Sie können sehen, dass dies tatsächlich in unserem parallelen Plan mit den Operatoren Index Seek und Hash Aggregate der Fall ist, die parallel ausgeführt werden.

Was die Kosten der Börsenbetreiber betrifft, so setzen sie sich aus Anfangskosten und einigen konstanten Kosten pro Zeile zusammen, die Sie leicht zurückentwickeln können.

Beachten Sie, dass in der einfachen parallelen Gruppierungs- und Aggregationsstrategie, die hier verwendet wird, die Kardinalitätsschätzungen in den seriellen und parallelen Plänen gleich sind. Das liegt daran, dass nur ein Aggregatoperator verwendet wird. Später werden Sie sehen, dass die Dinge anders sind, wenn Sie die lokale/globale Strategie verwenden.

Die folgenden Abfragen veranschaulichen die Auswirkungen der Anzahl der logischen CPUs und der Anzahl der beteiligten Zeilen auf die Abfragekosten (10 Abfragen mit Schritten von 100.000 Zeilen):

SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=900001GROUP BY empid; Empid auswählen, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=800001GROUP BY empid; Empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=700001GROUP BY empid auswählen; Empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=600001GROUP BY empid auswählen; Empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=500001GROUP BY empid auswählen; Empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empid auswählen; Empid auswählen, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; Empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=200001GROUP BY empid auswählen; Empid auswählen, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=100001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=000001GROUP BY empid;

Abbildung 8 zeigt die Ergebnisse.

Abbildung 8:Abfragekosten in Bezug auf #CPUs und #rows

Die grüne Linie stellt die Kosten der verschiedenen Abfragen (mit der unterschiedlichen Anzahl von Zeilen) unter Verwendung eines seriellen Plans dar. Die anderen Zeilen stellen die Kosten der parallelen Pläne mit unterschiedlicher Anzahl logischer CPUs und ihrer jeweiligen DOP für die Kalkulation dar. Die rote Linie stellt den Punkt dar, an dem die Kosten für serielle Abfragen 5 betragen – der Standardkostenschwellenwert für die Parallelitätseinstellung. Links von diesem Punkt (weniger zu gruppierende und zu aggregierende Zeilen) berücksichtigt der Optimierer normalerweise keinen parallelen Plan. Um die Kosten von Parallelplänen unterhalb der Kostenschwelle für Parallelität recherchieren zu können, haben Sie zwei Möglichkeiten. Eine Option besteht darin, den Abfragehinweis ENABLE_PARALLEL_PLAN_PREFERENCE zu verwenden, aber zur Erinnerung:Diese Option maximiert die Parallelität, anstatt sie nur zu erzwingen. Wenn das nicht der gewünschte Effekt ist, können Sie einfach die Kostenschwelle für Parallelität deaktivieren, etwa so:

EXEC sp_configure 'Erweiterte Optionen anzeigen', 1; NEU KONFIGURIEREN; EXEC sp_configure 'Kostenschwelle für Parallelität', 0; EXEC sp_configure 'Erweiterte Optionen anzeigen', 0; NEU KONFIGURIEREN;

Offensichtlich ist das in einem Produktionssystem kein kluger Schachzug, aber für Forschungszwecke durchaus nützlich. Das habe ich getan, um die Informationen für das Diagramm in Abbildung 8 zu erstellen.

Beginnend mit 100.000 Zeilen und dem Hinzufügen von 100.000-Schritten scheinen alle Diagramme zu implizieren, dass ein paralleler Plan immer vorgezogen worden wäre, wenn die Kostenschwelle für Parallelität kein Faktor gewesen wäre. Das ist in der Tat bei unseren Abfragen und der Anzahl der beteiligten Zeilen der Fall. Versuchen Sie es jedoch mit einer kleineren Anzahl von Zeilen, beginnend mit 10.000 und in Schritten von 10.000, indem Sie die folgenden fünf Abfragen verwenden (lassen Sie den Kostenschwellenwert für Parallelität vorerst deaktiviert):

SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=990001GROUP BY empid; Empid auswählen, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=980001GROUP BY empid; Empid auswählen, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=970001GROUP BY empid; Empid auswählen, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=960001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=950001GROUP BY empid;

Abbildung 9 zeigt die Abfragekosten sowohl mit seriellen als auch mit parallelen Plänen (Emulation von 4 CPUs, DOP für Kosten von 2).

Abbildung 9:Serial / paralleler Planschwellenwert

Wie Sie sehen können, gibt es einen Optimierungsschwellenwert, bis zu dem der serielle Plan bevorzugt wird und oberhalb dessen der parallele Plan bevorzugt wird. Wie bereits erwähnt, ist in einem normalen System, in dem Sie den Kostenschwellenwert für die Parallelitätseinstellung entweder auf dem Standardwert von 5 oder höher belassen, der effektive Schwellenwert ohnehin höher als in diesem Diagramm.

Zuvor habe ich erwähnt, dass die Kardinalitätsschätzungen der seriellen und parallelen Pläne gleich sind, wenn SQL Server die einfache Gruppierungs- und Aggregationsparallelismusstrategie auswählt. Die Frage ist, wie behandelt SQL Server die Kardinalitätsschätzungen für die lokale/globale Parallelitätsstrategie.

Um dies herauszufinden, verwende ich Abfrage 3 und Abfrage 4 aus unseren früheren Beispielen:

-- Abfrage 3:Local parallel global parallel SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Abfrage 4:Lokale parallele globale Seriennummer SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperid;

In einem System mit 8 logischen CPUs und einem effektiven DOP-Kostenwert von 4 erhielt ich die in Abbildung 10 gezeigten Pläne.

Abbildung 10:Kardinalitätsschätzung

Abfrage 3 gruppiert die Bestellungen nach Empid. 500 verschiedene Mitarbeitergruppen werden schließlich erwartet.

Abfrage 4 gruppiert die Bestellungen nach Liefer-ID. Letztendlich werden 5 verschiedene Versendergruppen erwartet.

Seltsamerweise scheint die Kardinalitätsschätzung für die Anzahl der vom lokalen Aggregat erzeugten Gruppen {Anzahl der von jedem Thread erwarteten unterschiedlichen Gruppen} * {DOP für die Kostenberechnung} zu sein. In der Praxis stellen Sie fest, dass die Anzahl normalerweise doppelt so hoch ist, da die DOP für die Ausführung (auch bekannt als DOP) zählt, die hauptsächlich auf der Anzahl der logischen CPUs basiert. Dieser Teil ist zu Forschungszwecken etwas schwierig zu emulieren, da der DBCC OPTIMIZER_WHATIF-Befehl mit der CPUs-Option die Berechnung von DOP für Kosten beeinflusst, aber DOP für die Ausführung nicht größer ist als die tatsächliche Anzahl von logischen CPUs, die Ihre SQL Server-Instanz sieht. Diese Zahl basiert im Wesentlichen auf der Anzahl der Planer, mit denen SQL Server beginnt. Sie können Steuern Sie die Anzahl der Planer, mit denen SQL Server beginnt, indem Sie den Startparameter -P{ #schedulers } verwenden, aber das ist ein etwas aggressiveres Recherchetool im Vergleich zu einer Sitzungsoption.

Auf jeden Fall hat meine Testmaschine ohne Emulation von Ressourcen 4 logische CPUs, was zu einem DOP für die Kosten von 2 und einem DOP für die Ausführung von 4 führt. In meiner Umgebung zeigt das lokale Aggregat im Plan für Abfrage 3 eine Schätzung von 1.000 Ergebnisgruppen (500 x 2) und tatsächlich 2.000 (500 x 4). In ähnlicher Weise zeigt das lokale Aggregat im Plan für Abfrage 4 eine Schätzung von 10 Ergebnisgruppen (5 x 2) und eine tatsächliche von 20 (5 x 4).

Wenn Sie mit dem Experimentieren fertig sind, führen Sie den folgenden Code zur Bereinigung aus:

-- Kostenschwelle für Parallelität auf Standardwert setzen EXEC sp_configure 'Erweiterte Optionen anzeigen', 1; NEU KONFIGURIEREN; EXEC sp_configure 'Kostenschwelle für Parallelität', 5; EXEC sp_configure 'Erweiterte Optionen anzeigen', 0; RECONFIGURE;GO -- OPTIMIZER_WHATIF-Optionen zurücksetzen DBCC OPTIMIZER_WHATIF(ResetAll); -- Indizes löschen DROP INDEX idx_oid_i_sid ON dbo.Orders;DROP INDEX idx_oid_i_eid ON dbo.Orders;DROP INDEX idx_oid_i_cid ON dbo.Orders;

Schlussfolgerung

In diesem Artikel habe ich eine Reihe von Parallelisierungsstrategien beschrieben, die SQL Server verwendet, um die Gruppierung und Aggregation zu handhaben. Ein wichtiges Konzept, das bei der Optimierung von Abfragen mit parallelen Plänen verstanden werden muss, ist der Parallelitätsgrad (DOP) für die Kostenrechnung. Ich habe eine Reihe von Optimierungsschwellen gezeigt, einschließlich einer Schwelle zwischen seriellen und parallelen Plänen und der Einstellungskostenschwelle für Parallelität. Die meisten der hier beschriebenen Konzepte beziehen sich nicht nur auf die Gruppierung und Aggregation, sondern gelten auch für Überlegungen zu parallelen Plänen in SQL Server im Allgemeinen. Nächsten Monat werde ich die Reihe fortsetzen, indem ich die Optimierung mit Abfrageumschreibungen erörtere.