Database
 sql >> Base de données >  >> RDS >> Database

Comment les plans parallèles démarrent - Partie 1

Cette série en cinq parties se penche en profondeur sur la façon dont les plans parallèles en mode ligne de SQL Server démarrent. Cette première partie couvre le rôle de la tâche mère (coordinateur) dans la préparation du plan d'exécution parallèle. Cela inclut l'initialisation de chaque opérateur et l'ajout de profileurs masqués pour collecter des données de performances d'exécution telles que le nombre réel de lignes et le temps écoulé.

Configuration

Pour fournir une base concrète à l'analyse, nous suivrons comment une requête parallèle particulière démarre l'exécution. J'ai utilisé le public Stack Overflow 2013 base de données (détails de téléchargement). La forme de plan souhaitée peut également être obtenue par rapport au plus petit ensemble de données Stack Overflow 2010 si cela est plus pratique. Il peut être téléchargé sur le même lien. J'ai ajouté un index non cluster :

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

Mon environnement de test est SQL Server 2019 CU9 sur un ordinateur portable avec 8 cœurs et 16 Go de mémoire alloués à l'instance. Niveau de compatibilité 150 est utilisé exclusivement. Je mentionne ces détails pour vous aider à reproduire le plan cible si vous le souhaitez. Les principes fondamentaux de l'exécution parallèle en mode ligne n'ont pas changé depuis SQL Server 2005, la discussion suivante est donc largement applicable.

La requête de test renvoie le nombre total de questions et de réponses, regroupées par mois et par année :

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
);

J'ai utilisé des astuces pour obtenir un plan de mode de ligne de forme particulière. L'exécution est limitée à DOP 2 pour rendre certains des détails présentés plus tard plus concis.

Le plan d'exécution estimé est (cliquez pour agrandir) :

Contexte

L'optimiseur de requête produit un seul plan compilé pour un lot. Chaque relevé du lot est marqué pour une exécution en série ou en parallèle, en fonction de l'éligibilité et des coûts estimés.

Un plan parallèle contient des échanges (opérateurs de parallélisme). Les échanges peuvent apparaître dans les flux de distribution , répartir les flux , ou regrouper des flux formulaire. Chacun de ces types d'échange utilise les mêmes composants sous-jacents, juste câblés différemment, avec un nombre différent d'entrées et de sorties. Pour plus d'informations sur l'exécution parallèle en mode ligne, consultez Plans d'exécution parallèles - Branches et threads.

Rétrogradation DOP

Le degré de parallélisme (DOP) d'un plan parallèle peut être déclassé lors de l'exécution si nécessaire. Une requête parallèle peut commencer par demander DOP 8, mais être progressivement rétrogradée à DOP 4, DOP 2 et enfin DOP 1 en raison d'un manque de ressources système à ce moment-là. Si vous souhaitez voir cela en action, regardez cette courte vidéo d'Erik Darling.

L'exécution d'un plan parallèle sur un seul thread peut également se produire lorsqu'un plan parallèle mis en cache est réutilisé par une session limitée à DOP 1 par un paramètre environnemental (par exemple, un masque d'affinité ou un gouverneur de ressources). Voir Mythe :SQL Server met en cache un plan en série avec chaque plan parallèle pour plus de détails.

Quelle qu'en soit la cause, la rétrogradation DOP d'un plan parallèle mis en cache ne entraîner l'élaboration d'un nouveau plan de série. SQL Server réutilise le plan parallèle existant en désactivant les échanges. Le résultat est un plan "parallèle" qui s'exécute sur un seul thread. Les échanges apparaissent toujours dans le plan, mais ils sont ignorés lors de l'exécution.

SQL Server ne peut pas promouvoir un plan série vers une exécution parallèle en ajoutant des échanges au moment de l'exécution. Cela nécessiterait une nouvelle compilation.

Initialisation du plan parallèle

Un plan parallèle contient tous les échanges nécessaires pour utiliser des threads de travail supplémentaires, mais il y a un travail de configuration supplémentaire nécessaire au moment de l'exécution avant que l'exécution parallèle puisse commencer. Un exemple évident est que des threads de travail supplémentaires doivent être alloués à des tâches spécifiques dans le plan, mais il y a bien plus que cela.

Je vais commencer au point où un plan parallèle a été extrait du cache de plan. À ce stade, seul le thread d'origine traitant la demande en cours existe. Ce fil est parfois appelé le "fil coordinateur" dans les plans parallèles, mais je préfère les termes "tâche parent » ou « parent travailleur ». Il n'y a par ailleurs rien de spécial à propos de ce fil; c'est le même thread que la connexion utilise pour traiter les demandes des clients et exécuter les plans en série jusqu'à leur achèvement.

Pour souligner le fait qu'un seul fil existe maintenant, je veux que vous visualisiez le plan à ce moment précis comme ceci :

J'utiliserai presque exclusivement des captures d'écran de Sentry One Plan Explorer dans cet article, mais pour cette première vue uniquement, je montrerai également la version SSMS :

Dans les deux représentations, la principale différence est l'absence d'icônes de parallélisme sur chaque opérateur, même si les échanges sont toujours présents. Seule la tâche parent existe actuellement et s'exécute sur le thread de connexion d'origine. Aucun thread de travail supplémentaire ont déjà été réservées, créées ou affectées à des tâches. Gardez à l'esprit la représentation du plan ci-dessus au fur et à mesure.

Création du plan exécutable

Le plan à ce stade n'est essentiellement qu'un modèle qui peut être utilisé comme base pour toute exécution future. Pour le préparer pour une exécution spécifique, SQL Server doit renseigner des valeurs d'exécution telles que l'utilisateur actuel, le contexte de la transaction, les valeurs des paramètres, les identifiants de tous les objets créés lors de l'exécution (par exemple, les tables et variables temporaires), etc.

Pour un plan parallèle, SQL Server doit effectuer un certain travail préparatoire supplémentaire pour amener la machinerie interne au point où l'exécution peut commencer. Le thread de travail de la tâche parent est responsable de l'exécution de presque tout ce travail (et certainement de tout le travail que nous couvrirons dans la partie 1).

Le processus de transformation du modèle de plan pour une exécution spécifique est appelé création du plan exécutable. . Il est parfois difficile de garder la terminologie droite, car les termes sont souvent surchargés et mal appliqués (même par Microsoft), mais je ferai de mon mieux pour être aussi cohérent que possible.

Contextes d'exécution

Vous pouvez penser à un contexte d'exécution en tant que modèle de plan contenant toutes les informations d'exécution spécifiques nécessaires à un thread particulier. Le plan exécutable pour une série consiste en un seul contexte d'exécution, où un seul thread exécute l'ensemble du plan.

Un parallèle le plan exécutable contient une collection de contextes d'exécution :un pour la tâche parente et un par thread dans chaque branche parallèle. Chaque thread de travail parallèle supplémentaire exécute sa partie du plan global dans son propre contexte d'exécution. Par exemple, un plan parallèle avec trois branches fonctionnant au DOP 8 a (1 + (3 * 8)) =25 contextes d'exécution. Les contextes d'exécution en série sont mis en cache pour être réutilisés, mais les contextes d'exécution parallèle supplémentaires ne le sont pas.

La tâche parent existe toujours avant toute tâche parallèle supplémentaire, elle est donc affectée au contexte d'exécution zéro . Les contextes d'exécution utilisés par les travailleurs parallèles seront créés ultérieurement, après l'initialisation complète du contexte parent. Les contextes supplémentaires sont clonés du contexte parent, puis personnalisé pour leur tâche spécifique (ceci est couvert dans la partie 2).

Il existe un certain nombre d'activités impliquées dans le démarrage du contexte d'exécution zéro. Il n'est pas pratique d'essayer de tous les énumérer, mais il sera utile de couvrir certains des principaux applicables à notre requête de test. Il y en aura encore trop pour une seule liste, je vais donc les diviser en sections (quelque peu arbitraires) :

1. Initialisation du contexte parent

Lorsque nous soumettons l'instruction pour exécution, le contexte de la tâche parente (contexte d'exécution zéro) est initialisé avec :

  • Une référence à la transaction de base (explicite, implicite ou auto-commit). Les nœuds de calcul parallèles exécuteront des sous-transactions, mais elles sont toutes incluses dans la transaction de base.
  • Une liste de paramètres de déclaration et leurs valeurs actuelles.
  • Un objet de mémoire principal (PMO) utilisé pour gérer les subventions et les allocations de mémoire.
  • Une carte liée des opérateurs (nœuds de requête) dans le plan exécutable.
  • Une usine pour tout gros objet requis (blob) poignées.
  • Classes de verrouillage pour garder une trace de plusieurs verrous détenus pendant une période pendant l'exécution. Tous les plans ne nécessitent pas de classes de verrouillage car les opérateurs entièrement en streaming verrouillent et déverrouillent généralement les lignes individuelles dans l'ordre.
  • La allocation de mémoire estimée pour la requête.
  • La mémoire en mode ligne accorde des commentaires structures pour chaque opérateur (SQL Server 2019).

Beaucoup de ces éléments seront utilisés ou référencés par des tâches parallèles ultérieurement, ils doivent donc d'abord exister dans la portée parente.

2. Métadonnées de contexte parent

Les prochaines tâches principales effectuées sont :

  • Vérification de l'estimation du coût de la requête est dans la limite définie par les options de configuration de limite de coût du gouverneur de requête.
  • Mettre à jour l'utilisation de l'index enregistrements - exposés via sys.dm_db_index_usage_stats .
  • Création de valeurs d'expression mises en cache (constantes d'exécution).
  • Création d'une liste d'opérateurs de profilage utilisé pour collecter des métriques d'exécution telles que le nombre de lignes et les délais, si cela a été demandé pour l'exécution en cours. Les profileurs eux-mêmes ne sont pas encore créés, juste la liste.
  • Prendre un instantané des attentes pour la fonctionnalité d'attente de session exposée via sys.dm_exec_session_wait_stats .

3. DOP et bourse de mémoire

Le contexte de la tâche parent maintenant :

  • Calcule le degré de parallélisme à l'exécution (DOP ). Ceci est affecté par le nombre de nœuds de calcul libres (voir "DOP downgrade" plus haut), où ils peuvent être placés parmi les nœuds, et un certain nombre d'indicateurs de trace.
  • Réserve le nombre requis de threads. Cette étape est purement comptable - les threads eux-mêmes peuvent ne pas exister à ce stade. SQL Server conserve une trace du nombre maximal de threads qu'il est autorisé à avoir. Réservation de fils soustrait de ce nombre. Lorsque les threads sont terminés, le nombre maximum est à nouveau augmenté.
  • Définit le délai d'expiration de l'octroi de mémoire .
  • Calcule l'allocation de mémoire, y compris la mémoire requise pour les tampons d'échange.
  • Acquiert l'allocation de mémoire via le sémaphore de ressource approprié .
  • Crée un objet gestionnaire pour gérer les sous-processus parallèles . La tâche parent est le processus de niveau supérieur; les tâches supplémentaires sont également appelées sous-processus .

Bien que les threads soient "réservés" à ce stade, SQL Server peut toujours rencontrer THREADPOOL attend plus tard lorsqu'il essaie d'utiliser l'un des threads "réservés". La réservation garantit que SQL Server restera autour de son nombre maximal de threads configuré à tout moment, mais le thread physique peut ne pas être immédiatement disponible à partir du pool de threads . Lorsque cela se produit, un nouveau thread devra être démarré par le système d'exploitation, ce qui peut prendre un peu de temps. Pour en savoir plus, consultez les attentes inhabituelles de THREADPOOL par Josh Darnell.

4. Configuration de l'analyse des requêtes

Les plans en mode ligne s'exécutent de manière itérative mode, en commençant par la racine. Le plan que nous avons en ce moment n'est pas encore capable de ce mode d'exécution. Il s'agit encore en grande partie d'un modèle, même s'il contient déjà une bonne quantité d'informations spécifiques à l'exécution.

SQL Server doit convertir la structure actuelle en une arborescence d'itérateurs , chacun avec des méthodes comme Open , GetRow , et Close . Les méthodes d'itération sont connectées à leurs enfants via des pointeurs de fonction. Par exemple, appeler GetRow à la racine appelle récursivement GetRow sur les opérateurs enfants jusqu'à ce qu'un niveau feuille soit atteint et qu'une ligne commence à « remonter » dans l'arborescence. Pour un rappel des détails, consultez Itérateurs, plans de requête et pourquoi ils s'exécutent à l'envers.

Fin de la partie 1

Nous avons bien avancé dans la configuration du contexte d'exécution de la tâche parent. Dans la partie 2, nous suivrons pendant que SQL Server construit l'arborescence d'analyse des requêtes nécessaire à l'exécution itérative.