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

So starten parallele Pläne – Teil 1

Diese fünfteilige Serie befasst sich eingehend damit, wie parallele Pläne im SQL Server-Zeilenmodus gestartet werden. Dieser erste Teil behandelt die Rolle der übergeordneten Aufgabe (Koordinator) bei der Vorbereitung des Plans für die parallele Ausführung. Es umfasst die Initialisierung jedes Operators und das Hinzufügen versteckter Profiler, um Laufzeitleistungsdaten wie die tatsächliche Zeilenanzahl und die verstrichene Zeit zu sammeln.

Einrichtung

Um eine konkrete Grundlage für die Analyse bereitzustellen, werden wir verfolgen, wie eine bestimmte parallele Abfrage mit der Ausführung beginnt. Ich habe den öffentlichen Stack Overflow 2013 verwendet Datenbank (Details herunterladen). Die gewünschte Planform kann auch anhand des kleineren Stack Overflow 2010-Datensatzes abgerufen werden, wenn dies bequemer ist. Es kann unter demselben Link heruntergeladen werden. Ich habe einen nicht gruppierten Index hinzugefügt:

CREATE NONCLUSTERED INDEX PP
ON dbo.Posts
(
    PostTypeId ASC,
    CreationDate ASC
);

Meine Testumgebung ist SQL Server 2019 CU9 auf einem Laptop mit 8 Kernen und 16 GB Arbeitsspeicher, der der Instanz zugewiesen ist. Kompatibilitätsstufe 150 ausschließlich verwendet wird. Ich erwähne diese Details, um Ihnen zu helfen, den Zielplan zu reproduzieren, wenn Sie dies wünschen. Die Grundlagen der parallelen Ausführung im Zeilenmodus haben sich seit SQL Server 2005 nicht geändert, daher ist die folgende Diskussion allgemein anwendbar.

Die Testabfrage gibt die Gesamtzahl der Fragen und Antworten zurück, gruppiert nach Monat und Jahr:

WITH 
    MonthlyPosts AS 
    (
        SELECT 
            P.PostTypeId, 
            CA.TheYear, 
            CA.TheMonth, 
            Latest = MAX(P.CreationDate)
        FROM dbo.Posts AS P
        CROSS APPLY 
        (
            VALUES
            (
                YEAR(P.CreationDate), 
                MONTH(P.CreationDate)
            )
        ) AS CA (TheYear, TheMonth)
        GROUP BY 
            P.PostTypeId, 
            CA.TheYear, 
            CA.TheMonth
    )
SELECT 
    rn = ROW_NUMBER() OVER (
        ORDER BY Q.TheYear, Q.TheMonth),
    Q.TheYear, 
    Q.TheMonth, 
    LatestQuestion = Q.Latest,
    LatestAnswer = A.Latest
FROM MonthlyPosts AS Q
JOIN MonthlyPosts AS A
    ON A.TheYear = Q.TheYear
    AND A.TheMonth = Q.TheMonth
WHERE 
    Q.PostTypeId = 1
    AND A.PostTypeId = 2
ORDER BY 
    Q.TheYear,
    Q.TheMonth
OPTION 
(
    USE HINT ('DISALLOW_BATCH_MODE'),
    USE HINT ('FORCE_DEFAULT_CARDINALITY_ESTIMATION'),
    ORDER GROUP,
    MAXDOP 2
);

Ich habe Hinweise verwendet, um einen bestimmten Formreihenmodusplan zu erhalten. Die Ausführung ist auf DOP 2 beschränkt, um einige der später gezeigten Details prägnanter zu machen.

Der geschätzte Ausführungsplan ist (zum Vergrößern klicken):

Hintergrund

Der Abfrageoptimierer erstellt einen einzelnen kompilierten Plan für einen Stapel. Jede Anweisung im Stapel ist je nach Berechtigung und geschätzten Kosten für die serielle oder parallele Ausführung markiert.

Ein paralleler Plan enthält Austausche (Parallelitätsoperatoren). Austausche können in Streams verteilen erscheinen , Streams neu partitionieren , oder Streams sammeln form. Jeder dieser Austauschtypen verwendet dieselben zugrunde liegenden Komponenten, nur anders verdrahtet, mit einer unterschiedlichen Anzahl von Ein- und Ausgängen. Weitere Hintergrundinformationen zur parallelen Ausführung im Zeilenmodus finden Sie unter Parallele Ausführungspläne – Branches und Threads.

DOP-Downgrade

Der Parallelitätsgrad (DOP) für einen parallelen Plan kann bei Bedarf zur Laufzeit herabgestuft werden. Eine parallele Abfrage beginnt möglicherweise mit der Anforderung von DOP 8, wird aber nach und nach auf DOP 4, DOP 2 und schließlich DOP 1 herabgestuft, da zu diesem Zeitpunkt Systemressourcen fehlen. Wenn Sie das in Aktion sehen möchten, sehen Sie sich dieses kurze Video von Erik Darling an.

Das Ausführen eines parallelen Plans auf einem einzelnen Thread kann auch passieren, wenn ein zwischengespeicherter paralleler Plan von einer Sitzung wiederverwendet wird, die durch eine Umgebungseinstellung (z. B. Affinitätsmaske oder Ressourcenkontrolle) auf DOP 1 beschränkt ist. Weitere Informationen finden Sie unter Mythos:SQL Server speichert einen seriellen Plan mit jedem parallelen Plan im Cache.

Was auch immer die Ursache sein mag, das DOP-Downgrade eines zwischengespeicherten parallelen Plans funktioniert nicht dazu führen, dass ein neuer Serienplan erstellt wird. SQL Server verwendet den vorhandenen parallelen Plan wieder, indem er deaktiviert wird die Börsen. Das Ergebnis ist ein „paralleler“ Plan, der auf einem einzigen Thread ausgeführt wird. Die Austauschvorgänge erscheinen weiterhin im Plan, werden aber zur Laufzeit umgangen.

SQL Server kann einen seriellen Plan nicht zur parallelen Ausführung heraufstufen, indem zur Laufzeit Austauschvorgänge hinzugefügt werden. Das würde eine neue Zusammenstellung erfordern.

Parallele Planinitialisierung

Ein paralleler Plan hat alle notwendigen Austauschvorgänge, um zusätzliche Worker-Threads zu nutzen, aber es gibt zusätzliche Einrichtungsarbeiten zur Laufzeit benötigt, bevor die parallele Ausführung beginnen kann. Ein offensichtliches Beispiel ist, dass zusätzliche Worker-Threads bestimmten Aufgaben innerhalb des Plans zugewiesen werden müssen, aber es gehört noch viel mehr dazu.

Ich werde an dem Punkt beginnen, an dem ein paralleler Plan aus dem Plan-Cache abgerufen wurde. An diesem Punkt existiert nur der ursprüngliche Thread, der die aktuelle Anforderung verarbeitet. Dieser Thread wird in parallelen Plänen manchmal als „Koordinator-Thread“ bezeichnet, aber ich bevorzuge die Begriffe „übergeordnete Aufgabe“. “ oder „Elternarbeitnehmer“. Ansonsten ist dieser Thread nichts Besonderes; es ist derselbe Thread, den die Verbindung verwendet, um Client-Anforderungen zu verarbeiten und serielle Pläne bis zum Abschluss auszuführen.

Um den Punkt zu betonen, dass nur ein einziger Thread existiert Jetzt möchte ich, dass Sie sich den Plan zu diesem Zeitpunkt wie folgt vorstellen:

Ich werde in diesem Beitrag fast ausschließlich Screenshots von Sentry One Plan Explorer verwenden, aber nur für diese erste Ansicht zeige ich auch die SSMS-Version:

In beiden Darstellungen besteht der Hauptunterschied im Fehlen von Parallelitätssymbolen bei jedem Operator, obwohl die Vermittlungsstellen immer noch vorhanden sind. Derzeit existiert nur die übergeordnete Aufgabe, die auf dem ursprünglichen Verbindungsthread ausgeführt wird. Keine zusätzlichen Worker-Threads bereits reserviert, erstellt oder Aufgaben zugewiesen wurden. Behalte die obige Plandarstellung im Hinterkopf, während wir weitermachen.

Erstellen des ausführbaren Plans

Der Plan ist an dieser Stelle im Wesentlichen nur eine Vorlage die als Grundlage für jede zukünftige Ausführung verwendet werden kann. Um es für eine bestimmte Ausführung vorzubereiten, muss SQL Server Laufzeitwerte wie den aktuellen Benutzer, den Transaktionskontext, Parameterwerte, IDs für alle zur Laufzeit erstellten Objekte (z. B. temporäre Tabellen und Variablen) usw. ausfüllen.

Für einen parallelen Plan muss SQL Server einiges an zusätzlicher Vorbereitungsarbeit leisten, um die interne Maschinerie an den Punkt zu bringen, an dem die Ausführung beginnen kann. Der Worker-Thread der übergeordneten Aufgabe ist für fast die gesamte Arbeit verantwortlich (und sicherlich für die gesamte Arbeit, die wir in Teil 1 behandeln werden).

Der Vorgang des Transformierens der Planvorlage für eine bestimmte Ausführung wird als Erstellen des ausführbaren Plans bezeichnet . Es ist manchmal schwierig, die Terminologie klar zu halten, da Begriffe oft überladen und falsch angewendet werden (sogar von Microsoft), aber ich werde mein Bestes tun, um so konsistent wie möglich zu sein.

Ausführungskontexte

Sie können sich einen Ausführungskontext vorstellen als Planvorlage, die mit allen spezifischen Laufzeitinformationen gefüllt ist, die von einem bestimmten Thread benötigt werden. Der ausführbare Plan für eine Seriennummer -Anweisung besteht aus einem einzelnen Ausführungskontext, in dem ein einzelner Thread den gesamten Plan ausführt.

Eine Parallele ausführbarer Plan enthält eine Sammlung von Ausführungskontexten :Eine für die übergeordnete Aufgabe und eine pro Thread in jedem parallelen Zweig. Jeder zusätzliche parallele Worker-Thread führt seinen Teil des Gesamtplans in seinem eigenen Ausführungskontext aus. Beispielsweise hat ein paralleler Plan mit drei Verzweigungen, die bei DOP 8 ausgeführt werden, (1 + (3 * 8)) =25 Ausführungskontexte. Serielle Ausführungskontexte werden zur Wiederverwendung zwischengespeichert, zusätzliche parallele Ausführungskontexte jedoch nicht.

Die übergeordnete Aufgabe existiert immer vor allen zusätzlichen parallelen Aufgaben, daher wird ihr der Ausführungskontext Null zugewiesen . Die von parallelen Workern verwendeten Ausführungskontexte werden später erstellt, nachdem der übergeordnete Kontext vollständig initialisiert wurde. Die zusätzlichen Kontexte werden geklont aus dem übergeordneten Kontext, dann angepasst für ihre spezifische Aufgabe (dies wird in Teil 2 behandelt).

Es gibt eine Reihe von Aktivitäten, die beim Starten des Ausführungskontexts Null involviert sind. Es ist unpraktisch zu versuchen, sie alle aufzulisten, aber es wird nützlich sein, einige der wichtigsten abzudecken, die auf unsere Testabfrage anwendbar sind. Es wird immer noch zu viele für eine einzelne Liste geben, also werde ich sie in (etwas willkürliche) Abschnitte aufteilen:

1. Initialisierung des übergeordneten Kontexts

Wenn wir die Anweisung zur Ausführung übermitteln, wird der Kontext der übergeordneten Aufgabe (Ausführungskontext Null) initialisiert mit:

  • Ein Verweis auf die Basistransaktion (explizit, implizit oder Auto-Commit). Parallele Worker führen Untertransaktionen aus, aber sie sind alle innerhalb der Basistransaktion enthalten.
  • Eine Liste von Anweisungs-Parametern und ihre aktuellen Werte.
  • Ein primäres Speicherobjekt (PMO) zur Verwaltung von Speicherzuteilungen und -zuweisungen.
  • Eine verknüpfte Karte der Operatoren (Abfrageknoten) im ausführbaren Plan.
  • Eine Fabrik für jedes benötigte große Objekt (Blob) Griffe.
  • Sperrklassen, um mehrere Sperren zu verfolgen, die während der Ausführung für einen bestimmten Zeitraum gehalten werden. Nicht alle Pläne erfordern Sperrklassen Da Full-Streaming-Operatoren normalerweise einzelne Zeilen nacheinander sperren und entsperren.
  • Die geschätzte Speicherzuteilung für die Abfrage.
  • Zeilenmodus-Speichergewährung Feedback Strukturen für jeden Operator (SQL Server 2019).

Viele dieser Dinge werden später von parallelen Aufgaben verwendet oder referenziert, daher müssen sie zuerst im übergeordneten Bereich vorhanden sein.

2. Metadaten des übergeordneten Kontexts

Die nächsten durchgeführten Hauptaufgaben sind:

  • Überprüfen der geschätzten Abfragekosten liegt innerhalb der durch die Konfigurationsoptionen für das Kostenlimit der Abfragekontrolle festgelegten Grenze.
  • Aktualisierung der Indexnutzung Datensätze – verfügbar gemacht durch sys.dm_db_index_usage_stats .
  • Zwischengespeicherte Ausdruckswerte erstellen (Laufzeitkonstanten).
  • Erstellen einer Liste von Profiling-Operatoren Wird verwendet, um Laufzeitmetriken wie Zeilenanzahl und Timings zu sammeln, wenn dies für die aktuelle Ausführung angefordert wurde. Die Profiler selbst sind noch nicht erstellt, nur die Liste.
  • Erstellen einer Momentaufnahme von Wartezeiten für die Sitzungswartefunktion, die über sys.dm_exec_session_wait_stats bereitgestellt wird .

3. DOP und Gedächtniszuschuss

Der übergeordnete Aufgabenkontext jetzt:

  • Berechnet Laufzeitgrad der Parallelität (DOP ). Dies wird durch die Anzahl der freien Arbeiter (siehe „DOP-Herabstufung“ weiter oben), wo sie zwischen den Knoten platziert werden können, und eine Reihe von Trace-Flags beeinflusst.
  • Reserviert die erforderliche Anzahl von Threads. Dieser Schritt ist reine Buchhaltung – die Threads selbst sind zu diesem Zeitpunkt möglicherweise noch nicht vorhanden. SQL Server verfolgt die maximal zulässige Anzahl von Threads. Threads reservieren subtrahiert von dieser Zahl. Wenn die Fäden fertig sind, wird die maximale Anzahl wieder erhöht.
  • Legt das Zeitlimit für die Speicherzuteilung fest .
  • Berechnet die Arbeitsspeicherzuteilung, einschließlich des für Austauschpuffer erforderlichen Arbeitsspeichers.
  • Erwirbt die Speicherzuteilung über das entsprechende Ressourcen-Semaphor .
  • Erzeugt ein Manager-Objekt, um parallele Unterprozesse zu handhaben . Die übergeordnete Aufgabe ist der Prozess der obersten Ebene; Zusätzliche Aufgaben werden auch als Unterprozesse bezeichnet .

Während Threads zu diesem Zeitpunkt „reserviert“ sind, kann SQL Server immer noch auf THREADPOOL stoßen wartet später, wenn es versucht, einen der „reservierten“ Threads zu verwenden. Die Reservierung garantiert, dass SQL Server jederzeit um die konfigurierte maximale Anzahl von Threads bleibt, aber der physische Thread ist möglicherweise nicht sofort aus dem Thread-Pool verfügbar . In diesem Fall muss vom Betriebssystem ein neuer Thread gestartet werden, was einige Zeit dauern kann. Weitere Informationen dazu finden Sie unter Unusual THREADPOOL Waits von Josh Darnell.

4. Scan-Setup abfragen

Zeilenmoduspläne werden iterativ ausgeführt Mode, beginnend bei der Wurzel. Der Plan, der uns im Augenblick vorliegt, ist dieser Ausführungsweise noch nicht gewachsen. Es handelt sich immer noch weitgehend um eine Vorlage, auch wenn es bereits eine beträchtliche Menge an ausführungsspezifischen Informationen enthält.

SQL Server muss die aktuelle Struktur in einen Baum von Iteratoren umwandeln , jeweils mit Methoden wie Open , GetRow , und Close . Die Iteratormethoden sind über Funktionszeiger mit ihren Kindern verbunden. Beispiel:Aufruf von GetRow auf der Wurzel ruft rekursiv GetRow auf an untergeordneten Operatoren, bis eine Blattebene erreicht ist und eine Reihe beginnt, den Baum „aufzublasen“. Eine Auffrischung der Details finden Sie unter Iteratoren, Abfragepläne und warum sie rückwärts ausgeführt werden.

Ende von Teil 1

Wir haben gute Fortschritte beim Einrichten des Ausführungskontexts für die übergeordnete Aufgabe gemacht. In Teil 2 werden wir mitverfolgen, wie SQL Server den für die iterative Ausführung erforderlichen Suchbaum für Abfragen erstellt.