Types d’EXPLAIN
AST— Arbre syntaxique abstrait.SYNTAX— Texte de la requête après les optimisations au niveau de l’AST.QUERY TREE— Arbre de requête après les optimisations au niveau du Query Tree.PLAN— Plan d’exécution de la requête.PIPELINE— Pipeline d’exécution de la requête.ANALYZE— Exécute la requête et annote le plan d’exécution avec les runtime metrics mesurées.ESTIMATE— Nombre estimé de lignes, de marks et de parts à lire depuis les tables pendant le traitement de la requête.TABLE OVERRIDE— Résultat validé d’une surcharge de table sur le schéma d’une fonction de table.
EXPLAIN AST
Affiche l’AST de la requête. Prend en charge tous les types de requêtes, pas uniquementSELECT.
Paramètres :
graph– Affiche l’AST sous forme de graphe, décrit dans le langage de description de graphes DOT. Par défaut : 0.
EXPLAIN SYNTAX
Affiche l’arbre syntaxique abstrait (AST) d’une requête après l’analyse syntaxique. Cela consiste à analyser la requête, à construire l’AST et l’arbre de requête, à exécuter éventuellement l’analyseur de requêtes et les passes d’optimisation, puis à reconvertir l’arbre de requête en AST. Paramètres :oneline– Affiche la requête sur une seule ligne. Valeur par défaut :0.run_query_tree_passes– Exécute les passes de l’arbre de requête avant d’en produire le dump. Valeur par défaut :0.query_tree_passes– Sirun_query_tree_passesest défini, indique combien de passes exécuter. Siquery_tree_passesn’est pas spécifié, toutes les passes sont exécutées.single_record– Renvoie la requête reformatée sous la forme d’un seul enregistrement sur plusieurs lignes au lieu d’un enregistrement par ligne. Valeur par défaut :1(contrôlée par le paramètreexplain_syntax_single_record). Définissez cette valeur sur0pour restaurer la sortie historique avec un enregistrement par ligne, ou définissezexplain_syntax_single_record = 0(globalement ou dansSETTINGSpar requête), ou définissezcompatibilitysur toute version antérieure à26.8.
Query
Response
run_query_tree_passes :
Query
Response
EXPLAIN QUERY TREE
Paramètres :run_passes— Exécute toutes les passes de l’arbre de requête avant d’en afficher le contenu. Valeur par défaut :1.dump_passes— Affiche des informations sur les passes utilisées avant d’afficher l’arbre de requête. Valeur par défaut :0.passes— Indique le nombre de passes à exécuter. S’il est défini sur-1, exécute toutes les passes. Valeur par défaut :-1.dump_tree— Affiche l’arbre de requête. Valeur par défaut :1.dump_ast— Affiche l’AST de la requête généré à partir de l’arbre de requête. Valeur par défaut :0.
EXPLAIN PLAN
Affiche les étapes du plan de requête. Paramètres :optimize— Détermine si les optimisations du plan de requête sont appliquées avant l’affichage du plan. Par défaut : 1.header— Affiche l’en-tête de sortie pour l’étape. Par défaut : 0.description— Affiche la description de l’étape. Par défaut : 1.indexes— Affiche les index utilisés, le nombre de parts filtrées et le nombre de granules filtrés pour chaque index appliqué. Par défaut : 0. Pris en charge pour les tables MergeTree. À partir de ClickHouse >= v25.9, cette instruction ne produit un résultat pertinent que lorsqu’elle est utilisée avecSETTINGS use_query_condition_cache = 0, use_skip_indexes_on_data_read = 0.projections— Affiche toutes les projections analysées et leur effet sur le filtrage au niveau des parts en fonction des conditions sur la clé primaire de la projection. Pour chaque projection, cette section inclut des statistiques comme le nombre de parts, de lignes, de marks et de plages évaluées à l’aide de la clé primaire de la projection. Elle indique également combien de parts de données ont été ignorées grâce à ce filtrage, sans lecture depuis la projection elle-même. Le champdescriptionpermet de déterminer si une projection a réellement été utilisée pour la lecture ou seulement analysée pour le filtrage. Par défaut : 0. Pris en charge pour les tables MergeTree.actions— Affiche des informations détaillées sur les actions de l’étape. Par défaut : 1.sorting— Affiche la description du tri pour chaque étape du plan produisant une sortie triée. Par défaut : 0.keep_logical_steps— Conserve les étapes logiques du plan pour les jointures au lieu de les convertir en implémentations physiques de jointure. Par défaut : 0.json— Affiche les étapes du plan de requête sous la forme d’une ligne au format JSON. Par défaut : 0. Il est recommandé d’utiliser le format TabSeparatedRaw (TSVRaw) pour éviter les séquences d’échappement inutiles.input_headers— Affiche les en-têtes d’entrée pour l’étape. Par défaut : 0. Utile principalement aux développeurs pour le Débogage des problèmes liés à une incompatibilité entre les en-têtes d’entrée et de sortie.column_structure— Affiche également la structure des colonnes dans les en-têtes, en plus de leur nom et de leur type. Par défaut : 0. Utile principalement aux développeurs pour le Débogage des problèmes liés à une incompatibilité entre les en-têtes d’entrée et de sortie.distributed— Affiche les plans de requête exécutés sur des nœuds distants pour les tables distribuées ou les répliques parallèles. N’est pas pris en charge avecjson. Par défaut : 0.compact— Lorsqu’il est activé, masque dans le plan les étapes d’expression et les informations détaillées sur les actions (entrées, fonctions, alias et positions de sortie). N’a d’effet que lorsqueactions = 1. Par défaut : 1.pretty— Affiche l’arborescence du plan à l’aide de caractères de dessin de lignes (├──, └──, │) au lieu de l’indentation, afin de visualiser la hiérarchie. Formate également les propriétés des étapes de jointure de manière intégrée. Par défaut : 1.
Par défaut,
explain_query_plan_default = 'pretty', donc actions, compact et pretty sont initialisés à 1 et le plan est affiché sous une forme compacte, soignée et annotée avec les actions. Le fait de spécifier explicitement l’une de ces options dans l’instruction EXPLAIN (par exemple, EXPLAIN actions = 0, compact = 0, pretty = 0 SELECT ...) remplace toujours la valeur par défaut.Avant ClickHouse 26.7, les valeurs par défaut de actions, compact et pretty étaient 0. Vous pouvez toujours obtenir cette sortie en définissant explain_query_plan_default = 'legacy' (globalement ou dans SETTINGS par requête), ou en définissant compatibility sur n’importe quelle version antérieure à 26.7.Les options json et distributed n’activent pas les valeurs par défaut de pretty (actions, compact et pretty), même lorsque explain_query_plan_default = 'pretty'. Pour inclure les détails des actions dans leur sortie, définissez manuellement actions = 1.L’estimation du coût des étapes et de la requête n’est pas prise en charge.
json = 1, le plan de requête est représenté au format JSON. Chaque nœud est un dictionnaire qui possède toujours les clés Node Type, Node Id et Plans. Node Type est une chaîne contenant le nom de l’étape, et Node Id est un identifiant d’étape unique (le nom de l’étape avec un suffixe numérique, par ex. Union_10). Plans est un tableau contenant les descriptions des étapes enfants. D’autres clés facultatives peuvent être ajoutées selon le type de nœud et les paramètres.
Exemple :
description = 1, la clé Description est ajoutée à l’étape :
header = 1, la clé Header est ajoutée à l’étape sous forme d’un tableau de colonnes.
Exemple :
indexes = 1, la clé Indexes est ajoutée. Elle contient un tableau des index utilisés. Chaque index est décrit en JSON avec la clé Type (une chaîne Partition Min-Max, Partition, Statistics, PrimaryKey ou Skip) et, éventuellement, les clés suivantes :
L’index Statistics utilise les statistiques de colonnes par part (valeurs min/max et nombre de valeurs NULL pour les colonnes Nullable) afin d’ignorer les parts qui ne peuvent pas correspondre au filter de la requête.
Name— Le nom de l’index (actuellement utilisé uniquement pour les indexSkip).Keys— Le tableau des colonnes utilisées par l’index.Condition— La condition utilisée.Description— La description de l’index (actuellement utilisée uniquement pour les indexSkip).Parts— Le nombre de parts après/avant l’application de l’index.Granules— Le nombre de granules après/avant l’application de l’index.Ranges— Le nombre de plages de granules après l’application de l’index.
projections = 1, la clé Projections est ajoutée. Elle contient un tableau de projections analysées. Chaque projection est décrite en JSON avec les clés suivantes :
Name— Le nom de la projection.Condition— La condition de clé primaire utilisée pour la projection.Description— La description de l’utilisation de la projection (par ex. filtrage au niveau des parties).Selected Parts— Nombre de parties sélectionnées par la projection.Selected Marks— Nombre de marques sélectionnées.Selected Ranges— Nombre de plages sélectionnées.Selected Rows— Nombre de lignes sélectionnées.Filtered Parts— Nombre de parties ignorées en raison du filtrage au niveau des parties.
actions = 1, les clés ajoutées dépendent du type d’étape.
Exemple :
compact = 0 et actions = 1, les étapes Expression sont visibles, accompagnées d’informations détaillées sur les expressions :
distributed = 1, la sortie inclut non seulement le plan de requête local, mais également les plans de requête qui seront exécutés sur les nœuds distants. Cela s’avère utile pour analyser et déboguer les requêtes distribuées.
distributed est affiché uniquement sous la forme legacy (non-pretty), car la sortie pretty n’intègre pas les plans des segments distants dans l’arborescence du plan. Pour cette raison, l’activation de distributed désactive automatiquement les options par défaut de pretty (actions, compact et pretty), quelle que soit la valeur de explain_query_plan_default. Vous pouvez toujours définir actions=1 manuellement. L’option distributed n’est pas non plus prise en charge avec json.pretty = 1, l’arborescence du plan est affichée à l’aide de caractères de dessin de lignes plutôt que par indentation, et des informations supplémentaires sont affichées pour les étapes clés :
- Les colonnes de sortie de la requête sont affichées en haut du plan.
- Les expressions dans les filtres, les clés d’agrégation, les descriptions de tri et les fonctions de fenêtre sont affichées dans une notation SQL lisible par l’humain (par ex.
a + 1 > 5au lieu degreater(plus(a, 1), 5)). Les préfixes internes des identifiants de colonnes (tels que__table1.) sont supprimés pour plus de clarté. - Les étapes source (telles que
ReadFromMergeTree) affichent leurs colonnes de sortie. - Les étapes de filtre affichent la condition de filtre en notation SQL. Lorsque des filtres de jointure à l’exécution sont présents, ils sont affichés séparément.
- Les étapes d’agrégation affichent les clés et les fonctions d’agrégation avec leurs arguments (par ex.
sum(c),count()). - Les ensembles IN issus de littéraux
Tupleaffichent leurs valeurs (tronquées pour les grands ensembles), les ensembles basés sur des sous-requêtes sont libelléssubquery1,subquery2, etc., et les ensembles provenant de tables utilisant le moteurSetaffichent le nom de la table. - Les étapes de jointure affichent la relation de jointure à l’aide d’une notation mathématique, les estimations que l’optimiseur d’ordre des jointures a établies pour l’étape (coût, sélectivité, lignes de sortie et lignes de chaque côté), ainsi que les colonnes d’entrée de chaque côté. Les symboles suivants sont utilisés pour représenter les différents types de JOIN :
Par exemple,
t1 ⟕ t2 signifie une jointure gauche entre les tables t1 et t2.
Le nombre entre crochets après le nom de la table (par ex. t1[100]) indique le nombre estimé de lignes
lorsque les statistiques de la table sont disponibles.
Sous la relation de jointure, chaque étape de jointure affiche les estimations établies pour elle par l’optimiseur d’ordre des jointures :
Coût— le coût de l’ensemble du sous-arbre de jointure sous cette étape, qui est la valeur que l’optimiseur minimise lorsqu’il compare les ordres de jointure candidats. Le coût d’une jointure est son nombre estimé de paires de lignes correspondantes,<selectivity> * <left_rows> * <right_rows>, plus le coût de ses entrées.Sélectivité— la fraction estimée du produit cartésien des deux côtés qui satisfait la condition de jointure. Elle est dérivée du nombre de valeurs distinctes (NDV) des clés de jointure : une égalité de clé conserve environ1 / max(NDV_left, NDV_right)des paires, et la plus petite fraction parmi les conditions de jointure est utilisée.Lignes de sortie— le nombre estimé de lignes produites par la jointure :<selectivity> * <left_rows> * <right_rows>pour une jointure interne, avec un minimum de<left_rows>pourLEFT, de<right_rows>pourRIGHTet de<left_rows> + <right_rows>pourFULL, car une jointure externe conserve chaque ligne de son côté préservé. Lorsqu’une jointureSEMIouANTIparticipe au réordonnancement, elle est estimée comme une fraction de son côté préservé,<preserved_rows> * min(1, <selectivity> * <other_rows>)pourSEMIet les lignes restantes pourANTI; sinon, elle utilise la formule correspondant à son type de jointure.Gauche/Droite— le nombre estimé de lignes entrant dans la jointure de chaque côté.
no stats. Cela se produit lorsque
l’optimisation de l’ordre des jointures n’a pas été exécutée — par exemple, lorsque
query_plan_optimize_join_order_limit
est défini sur 0 — ou lorsqu’il n’existe aucune base pour l’estimation.
Les lignes Input (left): et Input (right): répertorient les colonnes que chaque côté transmet à la jointure.
L’option pretty fonctionne bien avec compact = 1, qui masque les étapes Expression et les informations détaillées sur les actions, ce qui rend le plan plus facile à lire.
Exemple détaillé avec des jointures. Le paramètre
join_runtime_filter_min_probe_rows
est abaissé uniquement afin qu’une table aussi petite construise tout de même un filtre de jointure à l’exécution :
EXPLAIN PIPELINE
Paramètres :header— Affiche l’en-tête de chaque port de sortie. Valeur par défaut : 0.graph— Affiche un graphe décrit dans le langage de description de graphes DOT. Valeur par défaut : 0.compact— Affiche le graphe en mode compact si le paramètregraphest activé. Valeur par défaut : 1.compact_repeated_processor_chains— Regroupe les chaînes de processeurs répétées et adjacentes dans la sortie texte en n’affichant qu’une seule occurrence de la chaîne avec un nombre de répétitions. Cela peut faciliter la lecture des pipelines parallèles lorsque la même chaîne apparaît de nombreuses fois, par exemple dans des jointures. Cela n’affecte pas la sortie du graphe. Valeur par défaut : 0.
compact=0 et graph=1, les noms des processeurs contiendront un suffixe supplémentaire indiquant un identifiant de processeur unique.
Exemple :
EXPLAIN ANALYZE
EXPLAIN ANALYZE exécute effectivement la requête, ignore les lignes de résultat et affiche la même arborescence du plan que EXPLAIN PLAN, avec pour chaque étape des annotations indiquant ce qui s’est réellement produit à l’exécution.
Paramètres :
EXPLAIN ANALYZE accepte les mêmes options d’affichage que EXPLAIN PLAN (documentées dans la section EXPLAIN PLAN).
header— voir la section EXPLAIN PLAN.description— voir la section EXPLAIN PLAN.projections— voir la section EXPLAIN PLAN.sorting— voir la section EXPLAIN PLAN.input_headers— voir la section EXPLAIN PLAN.column_structure— voir la section EXPLAIN PLAN.actions— voir la section EXPLAIN PLAN. Par défaut : 1.indexes— voir la section EXPLAIN PLAN. Par défaut : 1.compact— voir la section EXPLAIN PLAN. Par défaut : 1.pretty— voir la section EXPLAIN PLAN. Par défaut : 1.processors— PourEXPLAIN ANALYZE, affiche une ligne supplémentaire par étape avec la répartition du temps écoulé par processeur :min,median,maxetsum. Utile pour repérer un déséquilibre de charge entre les processeurs parallèles. Par défaut : 0.matches— PourEXPLAIN ANALYZE, permet aux étapes de jointure d’effectuer le suivi supplémentaire nécessaire aux métriquesmatched,match rateetfanoutlorsque ces valeurs ne peuvent pas être déduites de ce que produit la jointure. Lorsqu’elles le peuvent, elles sont signalées sans cette option. Voir Étapes de jointure. Par défaut : 0.time— PourEXPLAIN ANALYZE, affiche des lignesTimeetConcurrencypour chaque étape, indiquant le temps d’horloge et le niveau de concurrence de l’étape et de sa branche. Voir Temps d’horloge et niveaux de concurrence des étapes et des branches. Par défaut : 1.
Comme
EXPLAIN ANALYZE exécute réellement la requête encapsulée, il se comporte comme cette
requête — et contrairement aux formes EXPLAIN qui n’exécutent rien — à plusieurs égards :- Quotas et limites. Il est comptabilisé dans les mêmes quotas
et soumis aux mêmes limites
(par ex.
query_selects,read_rows) que si la requête était exécutée directement. Les sources exemptées de quotas pendant la planification (commesystem.one) ne sont pas comptabilisées. - Transactions en échec. À l’intérieur d’une transaction
ayant déjà échoué (
ROLLED_BACK), il est rejeté avecINVALID_TRANSACTION, tout comme un simpleSELECT— exécutez d’abordROLLBACK. - Lectures en streaming. Sur une lecture en streaming (
FROM ... STREAM), il est rejeté avecNOT_IMPLEMENTED, car une telle lecture ne se termine jamais. - Requêtes distribuées. Il n’est pas pris en charge pour les requêtes exécutées en mode distributed.
Time— temps total, réparti entre les phases de planification (c.-à-d. création du plan + optimisation du plan + construction du pipeline) et d’exécution (exécution du pipeline).Execution— non affiché lorsquetime = 0. Découpe le temps d’exécution enin steps,outside stepsetidle. La somme de ces composantes correspond àexecution. Voir Temps d’horloge et niveaux de concurrence des étapes et des branches.Read— lignes et octets non compressés lus dans les tables, avec leur débit — les mêmes valeurs que celles indiquées dans le pied de page d’une requête classique sous la forme “Processed”.Peak memory— mémoire maximale utilisée par la requête.
I/O). Le temps et le parallélisme sont indiqués pour chaque étape de l’étape sur la ou les lignes en retrait qui suivent.
rows <in> → <out>— lignes entrées dans l’étape et qui en sont sorties ; (<selectivity>%) indique dans quelle mesure l’étape a filtré (out/in) ou étendu les données. Cette information est masquée lorsque le nombre de lignes en entrée est égal au nombre de lignes en sortie, ainsi que lorsque le nombre de lignes en entrée est égal à0.<bytes_in> → <bytes_out>— octets en mémoire non compressés traversant l’étape (omis lorsque les deux valeurs sont nulles).time <t> (<share>%)— temps d’horloge pendant lequel l’étape a été active, ainsi que sa part du temps d’exécution de la requête (c.-à-d. hors temps de build). Notez que les parts peuvent totaliser plus de 100 % car les étapes et les étapes s’exécutent en parallèle.parallelism <avg>/<max>— nombre moyen de threads CPU travaillant simultanément dans cette étape, par rapport au maximum qu’elle pouvait utiliser. Cette métrique est propre à l’étape : les threads travaillant au même moment sur d’autres étapes de la requête ne sont pas comptabilisés. Une valeur proche du maximum signifie que l’étape a été bien parallélisée ; une valeur proche de 1 signifie qu’elle s’est exécutée principalement en série.Stage (<stage>)— le nom de l’étape. Une étape avec une seule étape affiche directement la ligne de temps, sans libelléStage (...). Les étapes comportant plusieurs étapes affichent une ligne libellée par étape ; par exemple,AggregatingafficheStage (partial aggregation)etStage (final aggregation), et un hash join afficheStage (build)etStage (probe).
ClickHouse parallélise non seulement l’exécution des tâches au sein d’une étape du plan, mais aussi l’exécution des étapes du plan. La métrique
parallelism ne reflète que le travail de cette étape. D’autres étapes peuvent s’exécuter en parallèle ; ce nombre n’indique donc pas comment le parallélisme de l’étape se compare à celui de l’ensemble de la requête. Pour une vue à l’échelle de la requête, consultez la métrique Concurrency affichée avec time = 1 (Temps d’horloge et niveaux de concurrence des étapes et des branches).Le nombre maximal dans
parallelism est calculé comme le minimum entre :- le nombre total de tâches dans l’étape du plan ;
- le nombre maximal de threads de traitement des requêtes défini dans
max_threads.
Étapes de jointure
Pour une étape de jointure,EXPLAIN ANALYZE affiche des lignes comparant les estimations de l’optimiseur de requêtes d’ordre de jointure avec ce qui s’est réellement produit (voir Métriques de jointure estimées et réelles), ainsi que, pour chaque côté, des lignes de participation — Left et Right — suivies des éventuelles lignes propres à l’implémentation de la jointure. Left et Right correspondent aux côtés logiques de la requête SQL. Dans la plupart des cas, Left correspond également au côté sonde (probe) de la jointure, et Right au côté de construction (build). Ce n’est toutefois pas toujours le cas, en raison de l’inversion (swap) qui peut se produire lors de l’exécution de la jointure. Toutes les valeurs de join_algorithm sont prises en charge (hash, parallel_hash, grace_hash, partial_merge, full_sorting_merge, parallel_full_sorting_merge, direct), de même que les trois implémentations que ce paramètre ne peut pas sélectionner : les jointures CROSS ou COMMA, la jointure par boucles imbriquées par blocs utilisée pour une section ON ne déterminant aucune clé de jointure, et le moteur de table Join. La plupart indiquent les deux côtés ; certaines n’indiquent que le côté qu’elles matérialisent (par exemple, direct affiche uniquement Left:).
Les lignes associées à chaque côté ont la même structure :
EXPLAIN ANALYZE indique :
rows estimated <estimated_rows>— l’estimation, par l’optimiseur de requêtes d’ordre de jointure, du nombre de lignes de ce côté, affichée à titre de comparaison avec la valeur réellerowsplacée à côté ;no statslorsque l’optimiseur de requêtes n’a produit aucune estimation (voir Métriques de jointure estimées et réelles).rows <rows>— le nombre total de lignes de ce côté qui ont été traitées par la jointure.matched <matched_rows>— le nombre de lignes de ce côté qui ont trouvé au moins une ligne correspondante de l’autre côté. Il s’agit bien de lignes, et non de clés : si une clé apparaît trois fois à droite et établit une correspondance, les trois lignes de droite sont comptées comme correspondantes.match rate <match_rate>%— le pourcentage de lignes de ce côté ayant établi une correspondance, calculé comme suit :100 * <matched_rows> / <rows>.fanout <fanout>— le nombre de lignes de sortie produites en moyenne par une ligne correspondante de ce côté.
not collected plutôt que par 0. match rate et fanout sont calculés à partir de matched ; par conséquent, pour un côté dépourvu de cette valeur, ces trois valeurs sont indiquées comme not collected.
Métriques de join estimées et réelles
Une étape de jointure contient les estimations de l’optimiseur d’ordre de jointure — les mêmes que celles affichées parEXPLAIN PLAN (voir la section EXPLAIN PLAN) — et EXPLAIN ANALYZE affiche chacune d’elles à côté de la valeur mesurée :
Cost— les deux valeurs comptabilisent les lignes de sortie correspondantes. L’estimation est le coût, selon l’optimiseur, du sous-arbre de jointure :<selectivity> * <left_rows> * <right_rows>, auquel s’ajoute le coût de ses entrées. La valeur réelle est mesurée de la même manière : les lignes de sortie correspondantes de cette jointure, plus le coût réel de chaque jointure située en dessous et appartenant au même cluster de réordonnancement.Selectivity— l’estimation est calculée à partir du nombre de valeurs distinctes des clés de jointure ; la valeur réelle est la fraction mesurée du produit cartésien qui se retrouve dans la sortie :<matched output rows> / (<left rows> * <right rows>), puisque c’est ce que l’optimiseur cherche à estimer.Output rows— le nombre estimé et réel de lignes produites par la jointure. Lorsque les deux valeurs sont non nulles,q-errorindiquemax(estimated / actual, actual / estimated), la mesure standard de la qualité de l’estimation de cardinalité :1.00correspond à une estimation parfaite, tandis qu’une valeur élevée signifie que l’optimiseur a choisi l’ordre des jointures sur la base d’une estimation de cardinalité très erronée.
no stats — par exemple, lorsque l’optimisation de l’ordre des jointures n’a pas été exécutée parce que query_plan_optimize_join_order_limit vaut 0. Cela se distingue de not collected, qui indique une valeur réelle que l’exécution n’a pas pu mesurer. Les jointures dont le côté droit est prérempli (le moteur de table Join, les jointures direct) ne passent pas par l’optimiseur d’ordre des jointures et n’affichent que les lignes de participation.
L’étape de jointure affiche également les lignes Input (left): et Input (right):, avec les colonnes fournies par chaque côté à la jointure.
Pour les tables de l’exemple de jointure EXPLAIN PLAN, l’étape de jointure de EXPLAIN ANALYZE SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id se présente comme suit :
Fanout
fanout mesure la multiplication des lignes :
NULL pour chaque ligne d’un côté préservé qui n’a trouvé aucune correspondance. Ces lignes sont soustraites afin de ne pas diluer le ratio. Seul un côté préservé en comporte : le côté droit pour RIGHT et FULL, le côté gauche pour LEFT et FULL :
fanout = 0— les lignes correspondantes n’ont produit aucune ligne de sortie, comme dans une jointureANTI: elle ne produit que les lignes qui n’ont trouvé aucune correspondance.fanout = 1— une jointure 1:1 sans duplication ; chaque ligne correspondante a produit exactement une ligne de sortie.fanout > 1— une jointure 1:N ; des clés dupliquées de l’autre côté ont multiplié les lignes. Une valeur élevée des deux côtés simultanément indique une explosion cartésienne involontaire.
Cas où les nombres nécessitent matches = 1
La plupart de ces nombres résultent des données que la jointure produit déjà et sont rapportés par un simple EXPLAIN ANALYZE. Les autres nécessitent un suivi que la jointure n’effectuerait pas autrement et ne sont donc rapportés qu’avec EXPLAIN ANALYZE matches = 1. Les cas concernés dépendent de l’algorithme ; dans la famille hash, il y en a deux :
- le côté droit de
ALL INNERetALL LEFT, qui nécessite de marquer chaque ligne droite correspondante ; - le côté gauche de
ALL LEFTetALL FULL, mais uniquement lorsque la requête ne sélectionne aucune donnée de la table de droite et que la sectionONest une simple égalité de clé. Sinon, la sonde enregistre déjà les lignes de gauche qui correspondent — soit pour matérialiser les colonnes de droite, soit pour évaluer la condition résiduelle — et le décompte est exact sans cette option.
partial_merge en a besoin pour le côté droit des quatre types ALL, pour la même raison.
full_sorting_merge et parallel_full_sorting_merge en ont besoin pour les deux côtés des types ANY. Les
types ALL n’en ont pas besoin.
L’option est désactivée par défaut, car ce suivi supplémentaire a un coût : il réduit la fidélité des mesures. Le travail est effectué dans la boucle de sonde et augmente avec le nombre de lignes de sortie. Utilisez
matches = 1 lorsque vous devez connaître les correspondances exactes que les lignes de gauche et de droite ont trouvées de l’autre côté.matches = 1 ne permet pas de collecter toutes les combinaisons. Le côté qu’une jointure peut rapporter découle
de ce que cette jointure doit déjà faire ; cela dépend donc de l’algorithme, ainsi que du type et de la
strictness.
Famille hash. hash, parallel_hash et grace_hash donnent toujours les mêmes résultats :
Le côté droit n’est pas disponible lorsque la jointure ne conserve qu’une ligne par clé dans sa table de hachage, comme le font les jointures
ANY, SEMI et ANTI : les lignes de droite dupliquées ne sont jamais stockées et ne peuvent donc pas être comptées. Le côté gauche n’est pas disponible lorsque la jointure supprime la sortie d’une ligne de gauche dont le partenaire a déjà été revendiqué par une autre ligne de gauche, de sorte que les lignes émises sous-estiment les lignes correspondantes.
L’activation de any_join_distinct_right_table_keys fait passer ANY à l’ancienne sémantique RightAny, qui émet une ligne par ligne de gauche et conserve donc les deux décomptes. ANY RIGHT et ANY FULL rapportent alors les deux côtés, et ANY INNER est réécrit en SEMI LEFT.
Le moteur de table Join suit le même tableau, en utilisant le type et la strictness déclarés dans le moteur : Join(ALL, INNER, …) rapporte les deux côtés, Join(ANY, LEFT, …) aucun des deux.
Algorithmes de fusion. full_sorting_merge et parallel_full_sorting_merge acceptent les quatre types ALL, ANY INNER, ANY LEFT, ANY RIGHT, ASOF et ASOF LEFT. Ils rapportent les deux côtés pour tous les types, sauf ASOF et ASOF LEFT, pour lesquels le côté droit est not collected, et ce sans matches = 1 — ils parcourent les deux entrées triées et voient chaque ligne d’une plage d’égalité au fur et à mesure qu’ils la consomment ; rien n’a donc besoin d’être reconstruit par la suite.
partial_merge accepte ALL INNER, ALL LEFT, ALL RIGHT, ALL FULL, ANY INNER, ANY LEFT et SEMI LEFT. Il rapporte les deux côtés pour les quatre types ALL, le côté droit avec matches = 1 ; pour ANY INNER, ANY LEFT et SEMI LEFT, le côté droit est not collected.
direct. Le côté gauche uniquement. Le côté droit est un magasin clé-valeur qui n’est jamais matérialisé sous forme de lignes ; il n’a donc pas de ligne Right:.
CROSS, COMMA et un ON constant. Aucun côté, comme décrit ci-dessus.
Jointure par boucles imbriquées par blocs (block nested loop join). Il s’agit de l’opérateur qui prend en charge une section ON ne déterminant aucune clé de jointure, à moins que l’un des chemins qui le précèdent ne revendique la condition (voir allow_block_nested_loop_join). Il accepte tous les types et toutes les strictness, à l’exception de ASOF, PASTE et ANY FULL :
Le côté gauche est rapporté sans l’option partout où l’opérateur enregistre déjà quelles lignes de gauche ont trouvé une correspondance : tout type qui conserve les lignes de gauche sans correspondance a besoin de cet enregistrement, tout comme toute strictness qui retient au plus une paire par ligne de gauche. Le côté droit est rapporté partout où l’opérateur conserve ses indicateurs de correspondance, ce qui est nécessaire pour
RIGHT, FULL et pour un SEMI/ANTI piloté par la droite. ANY INNER les conserve aussi, mais uniquement pour attribuer une ligne de droite à une seule ligne de gauche : il cesse de revendiquer pour une ligne de gauche qui a déjà sa paire. Les indicateurs marquent donc les paires du résultat plutôt que chaque correspondance, et ne sont pas rapportés. ALL INNER n’atteint l’opérateur que lorsque hash est désactivé ; sinon, la condition devient un CROSS JOIN assorti d’un filter.
L’activation de any_join_distinct_right_table_keys fait ici aussi basculer ANY vers la sémantique RightAny, qui rapporte le côté gauche pour tous les types et le côté droit pour RIGHT et FULL ; ANY INNER est réécrit en SEMI LEFT.
Lorsque deux algorithmes rapportent chacun un nombre, ces nombres concordent. Les algorithmes de fusion disposent simplement de davantage d’informations ; ils ne divergent pas sur la définition d’une correspondance.
Lignes spécifiques à l’algorithme
Examinons les lignes supplémentaires ajoutées par chaque implémentation de jointure. Pour les jointureshash et parallel_hash, ainsi que pour le moteur de table Join, une ligne Hash table: décrit la table de hachage construite à partir de la table de droite :
unique keys <unique_keys>— le nombre de clés uniques stockées dans la table de hachage pendant la phase de construction.memory <peak_memory>— le pic de mémoire utilisé par la table de hachage pendant la phase de construction.
grace_hash, la ligne Hash table: indique également comment la jointure s’est adaptée à la limite de mémoire, et une ligne Spill: indique si des données ont été déversées sur le disque :
buckets <buckets>— le nombre de buckets auquel la jointure grace hash join est parvenue à la fin de l’exécution. Il s’agit toujours d’une puissance de2.rehashes <rehashes>— le nombre de fois où le nombre de buckets a dû être doublé pour respecter la limite de mémoire.Spill:— un indicateuryes/noprécisant si des données ont été écrites sur le disque. Lorsque c’est le cas,left spilled <left_spilled_bytes>etright spilled <right_spilled_bytes>indiquent le nombre d’octets compressés écrits sur le disque depuis les côtés gauche (sonde) et droit (construction) ; si aucune donnée n’a été écrite sur le disque, la ligne est simplementSpill: no.
partial_merge, la ligne Right: contient des informations supplémentaires sur la mise en mémoire tampon et le tri de la table de droite, et le temps de tri s’affiche sur les lignes Stage (build) et Stage (probe) :
size <right_size>— la taille en mémoire des blocs de la table de droite.blocks <right_blocks>— le nombre de blocs dans lesquels la table de droite a été mise en mémoire tampon.storage <in-memory|external>— indique si la table de droite tenait en mémoire (in-memory) ou devait être déversée sur le disque (external). Lorsqu’il estexternal, un champ supplémentairespilled <spilled_bytes>indique le nombre d’octets compressés écrits sur le disque.sort time <sort_time>— le temps consacré au tri de la table de droite (lors de l’étape de construction) et de chaque bloc gauche entrant (lors de l’étape de sonde).sort share <sort_share>%—sort timeen proportion du temps d’activité de cette étape (la somme du temps écoulé de ses processeurs), contrairement au pourcentage detimede l’étape, qui représente une part du temps d’exécution de l’ensemble de la requête.
full_sorting_merge, seules les lignes communes Left: et Right: sont affichées.
Pour la jointure direct, seule la ligne Left: est affichée, car le côté droit est un magasin clé-valeur consulté directement plutôt que matérialisé en lignes.
Pour une jointure CROSS ou COMMA, ainsi que pour une jointure ALL INNER JOIN dont la section ON ne contient aucune égalité de clés (le planificateur la transforme alors en CROSS JOIN, avec cette condition comme filter), une ligne Buffer: décrit comment la table de droite a été conservée en mémoire et une ligne Spill: indique si elle a été écrite sur le disque :
memory <peak_memory>— le pic de mémoire occupé par la table de droite mise en mémoire tampon.compressed <yes|no>— indique si au moins un bloc mis en mémoire tampon a été compressé ; les lecteurs décompressent alors chaque bloc stocké.Spill:— le même indicateuryes/noque pourgrace_hash,right spilled <right_spilled_bytes>indiquant le nombre d’octets compressés écrits sur le disque.
matched not collected : un prédicat constant associe soit chaque ligne de gauche à chaque ligne de droite, soit aucune ligne ; il n’est donc pas possible d’indiquer quelles lignes individuelles correspondent.
La jointure par boucles imbriquées par blocs, qui traite toutes les autres sections ON dépourvues d’égalité de clés, affiche les deux mêmes lignes concernant la table de droite qu’elle matérialise :
memory <peak_memory>— le pic de mémoire occupé par la table de droite stockée. Cette valeur vaut0lorsque chaque bloc a été écrit sur disque dès son arrivée, puisqu’aucun d’entre eux n’a jamais été conservé en mémoire.compressed <yes|no>— indique si au moins un bloc stocké a été compressé ; les lecteurs décompressent alors chacun des blocs stockés.Spill:— le même indicateuryes/noque pourgrace_hash,right spilled <right_spilled_bytes>indiquant le nombre d’octets compressés écrits sur disque.
CROSS, les deux côtés peuvent ici indiquer matched, puisque l’opérateur évalue une véritable condition sur chaque paire qu’il examine ; le tableau ci-dessus précise lequel des deux le fait.
Pour une jointure utilisant le moteur de table Join, les deux côtés sont indiqués, ainsi que la ligne Hash table: qui décrit la table préconstruite. Le côté droit comptabilise les lignes stockées dans le moteur, et non celles créées pour chaque requête.
Temps par processeur
Avecprocessors = 1, une ligne supplémentaire est affichée sous chaque étape, montrant la répartition du temps écoulé entre les processeurs de l’étape :
<n> correspond au nombre de processeurs dans l’étape. Un écart important entre median et max indique un déséquilibre de charge entre les processeurs parallèles.
Temps d’horloge et niveaux de concurrence des étapes et des branches
Sauf sitime = 0, deux lignes supplémentaires sont affichées pour chaque étape du plan de requête :
step désigne l’étape elle-même. branch désigne le sous-arbre dont cette étape est la racine : l’étape et toutes celles qui se trouvent en dessous dans le plan.
Time— la durée pendant laquelle au moins un thread a travaillé sur l’étape (step) ou sur une étape de son sous-arbre (branch).<share>exprime cette durée en pourcentage du temps d’exécution de la requête.Concurrency— le nombre moyen de threads occupés à exécuter n’importe quelle étape de la requête, et pas seulement celle-ci, pendant que l’étape (step) ou son sous-arbre (branch) était actif.<m>est le nombre maximal de threads que la requête pouvait utiliser. Les étapes qui ne possèdent aucun processeur, par exempleUnionlorsque ses entrées ont déjà le même en-tête, affichentUnknownpourstep: il n’y a aucun travail à mesurer. La valeur debranchreste indiquée.
Time d’une étape correspond à la durée de l’union de ses intervalles. Les périodes pendant lesquelles aucun thread ne travaille sur l’étape ne sont pas comptabilisées. Pour calculer la valeur Time d’une branche, on réunit d’abord les intervalles de toutes les étapes de son sous-arbre.
Le branch de l’étape racine couvre l’ensemble du plan, mais sa part est normalement inférieure à 100 %. La ligne Execution du récapitulatif de la requête indique à quoi correspond le reste du temps d’exécution (voir le récapitulatif de la requête dans l’exemple ci-dessus) :
in steps— l’union des intervalles de toutes les étapes du plan. Il s’agit de la duréebranchde l’étape racine, et la part indiquée est identique.outside steps— le temps pendant lequel au moins un thread exécutait un processeur, sans qu’aucun de ces processeurs n’appartienne à une étape de ce plan : le sink de sortie,Resize, les transformations de conversion et les étapes de plans non accessibles depuis celui-ci.idle— le temps pendant lequel aucun thread n’exécutait de processeur : le démarrage et l’arrêt de l’executor, ainsi que les moments où tous les threads étaient en attente.
Time, les parts de time par étape et les parts de Execution utilisent le même dénominateur ; elles peuvent donc être comparées entre elles.
La valeur Concurrency correspond au temps d’activité cumulé de tous les threads de la requête pendant cette union, divisé par la durée de l’union. Elle compte les threads occupés à exécuter n’importe quelle étape de la requête, contrairement à la métrique parallelism par étape, qui ne compte que les threads de l’étape concernée. Elle répond à la question : “pendant que cette étape (ou branche) était active, dans quelle mesure la requête dans son ensemble mobilisait-elle ses threads ?” Une valeur proche de <m> signifie que la requête est restée entièrement parallélisée pendant que cette étape était active. Une valeur proche de 1 signifie que la requête s’exécutait de manière sérialisée pendant cette période : pour step, le seul thread occupé travaillait sur cette étape elle-même, ce qui en fait un point de sérialisation ; pour branch, le thread travaillait quelque part dans le sous-arbre, pas nécessairement sur cette étape. Une étape sérialisée ne constitue un véritable goulot d’étranglement que si sa part de Time est également significative.
EXPLAIN ESTIMATE
Affiche le nombre estimé de lignes, de marks et de parts à lire dans les tables lors du traitement de la requête. Fonctionne avec les tables de la famille MergeTree. Exemple Création d’une table :Query
Query
Response
EXPLAIN WHATIF
Estime le bénéfice qu’un index de saut hypothétique apporterait à une requêteSELECT, sans matérialiser l’index sur disque. Définissez un ou plusieurs candidats avec CREATE HYPOTHETICAL INDEX, puis exécutez EXPLAIN WHATIF SELECT ... pour obtenir, pour chaque candidat : l’applicabilité, le nombre estimé de marques lues, le nombre estimé d’octets et le taux d’exclusion.
Les projections hypothétiques définies avec CREATE HYPOTHETICAL PROJECTION sont également des candidats. Pour une projection ordinaire, l’estimation construit son index primaire en mémoire sur les parts que la requête lirait, puis l’élague comme le serait une projection matérialisée.
Le rapport indique les marques et les lignes que la projection lirait, un read_ratio par rapport à la lecture de la table de base, ainsi qu’un verdict indiquant si l’optimiseur de requêtes la choisirait. Avec force_optimize_projection = 1 ou prefer_optimize_projection = 1, l’optimiseur de requêtes utilise toute projection exploitable ; une projection qu’il ne retiendrait pas sur la base du coût est donc signalée comme chosen (forced).
Lorsque le résultat dépend de la disposition de la part de projection, marks_span indique la plage de valeurs, et une décision trop incertaine pour trancher est signalée comme too close to call. Sur les tables volumineuses, l’estimation lit un échantillon de granules (voir projection_scan_budget_rows).
Les projections d’agrégation, les projections comportant une clause WHERE, leurs propres index de saut, une clé basée sur l’ordre de commit ou sur une colonne virtuelle, ou un _block_number stocké, ainsi que les projections qui ne couvrent pas toutes les colonnes lues par la requête, sont signalées comme not_applicable. force_optimize_projection_name et preferred_optimize_projection_name sont ignorés, et force_optimize_projection ne fait pas échouer le statement.
Syntaxe
projection_scan_budget_rows— nombre de lignes qu’une estimation de projection peut lire avant de basculer sur un échantillon de granules. Valeur par défaut :10000000.0signifie aucune limite ;max_rows_to_readpeut l’abaisser. Un échantillon lit au moins une trentaine de granules, avec au moins un granule par part ; lorsquemax_rows_to_readne le permet pas, l’estimation estunsupported.empirical—1(par défaut) évalue l’index en mémoire sur les granules restant après le filtrage initial pour mesurer le taux d’exclusion (une borne supérieure).0saute cette étape. Dans les deux cas, siempiricalne produit aucun résultat (parce qu’il est désactivé ou que l’index ne peut pas être évalué en mémoire), l’estimateur utilise les statistiques des colonnes. Si elles ne sont pas disponibles non plus, il fournit un résumé indiquant uniquement si l’index est applicable.
source— indique comment l’estimation a été obtenue.empirical: l’index a été construit en mémoire sur les granules conservés après l’élagage de référence, puis les granules que l’index permettrait d’ignorer ont été comptés. Il s’agit d’une borne supérieure — voir les limites dansCREATE HYPOTHETICAL INDEX.statistical: dérivée des statistiques de colonnes. Utilisée lorsque l’estimation empirique est désactivée (empirical = 0) ou n’a pas pu produire de résultat, et que des statistiques de colonnes sont définies sur les colonnes concernées.applicability_only: l’index s’applique au prédicat, mais ni l’estimation empirique ni l’estimation statistique n’ont produit de résultat (par ex.empirical = 0et aucune statistique de colonnes définie). Indiqueskip_ratio: 0.0%comme borne prudente.
empirical_reason— indique pourquoi l’estimation empirique n’a pas pu être exécutée. Affiché uniquement avecempirical_status: unsupported. Par exemple, une valeur non nulle demerge_tree_min_rows_for_seekou demerge_tree_min_bytes_for_seekentraîne le regroupement des plages de marques lors d’une lecture réelle, ce que le comptage par granule ne modélise pas ; l’estimation se rabat donc surstatisticalouapplicability_only.sampled_parts/sampled_marks—<baseline-pruned> / <total in the table>. Indique quelle fraction de la table reste après l’élagage par la PK, la partition et les index existants, c.-à-d. l’entrée de l’index hypothétique. Pour une projection,sampled_marksvaut<marks read> / <marks in the parts the query reads>; si le premier nombre est inférieur au second, l’estimation a reposé sur un échantillon.est_bytes— estimation du nombre d’octets lus, dérivée de la taille moyenne des lignes de la table ; elle est donc approximative et varie selon le stockage et la compression. La ligne de référence n’apparaît que lorsque la requête lit des lignes ; la ligne par candidat n’apparaît que lorsque l’estimation de référence des octets est connue.
WHATIF et SELECT — il n’y a pas de mot-clé SETTINGS (comme pour les autres variantes de EXPLAIN, qui acceptent leurs options de cette manière).
Si ni index hypothétiques ni projections hypothétiques ne sont définis pour la table, EXPLAIN WHATIF indique status: not_applicable avec une suggestion d’en créer un.
Ligne combinée (plusieurs candidats)
Lorsque deux candidats ou plus sont évalués de manière empirique, EXPLAIN WHATIF ajoute un bloc nommé (combined: idx_a, idx_b, ...) après les lignes de chaque candidat. Ce bloc indique le bénéfice cumulé de tous ces index : une lecture réelle ne conserve un granule que s’il n’est écarté par aucun des index de saut. L’estimation combinée correspond donc à l’intersection des granules conservés par les candidats. Son skip_ratio est ainsi au moins aussi élevé que celui du meilleur candidat pris isolément : des index complémentaires élaguent davantage de données ensemble, tandis que des index redondants ne le modifient pas.
Seuls les candidats avec source: empirical sont pris en compte, car la ligne combinée est construite en calculant l’intersection de leurs ensembles de granules conservés. Les candidats estimés avec statistical ou applicability_only ne disposent pas de données par granule et sont exclus ; le bloc combiné n’apparaît donc que si au moins deux candidats ont produit une estimation empirique. Dans les autres cas, il est omis (par exemple avec empirical = 0). Ses champs d’estimation sont identiques à ceux d’un bloc empirique par candidat, sauf elapsed_us, qui vaut 0 : l’estimation combinée est dérivée des parcours effectués pour chaque candidat, et non d’un nouveau parcours. Le nom synthétique (combined: ...) sert uniquement de libellé dans le rapport et ne peut pas être utilisé avec force_data_skipping_indices.
Exemple empirique
minmax ramènerait le nombre de marques à lire de 100 à 1 — skip_ratio: 99.0%. (est_bytes est une estimation fondée sur la taille moyenne des lignes ; sa valeur exacte peut donc varier.)
Exemple avec statistiques
Les statistiques de colonne sont désactivées par défaut. Pour utiliser le chemin statistical, définissez-les d’abord sur les colonnes concernées, puis attendez que la mutation de matérialisation se termine :
b < 10 (environ 10 lignes sur 10 000) et est indiqué comme borne supérieure de skip_ratio. Il n’y a ni sampled_parts ni sampled_marks — aucune donnée n’a été lue.
Si aucun des deux mécanismes n’est disponible (par ex. empirical = 0 et aucune statistique de colonne définie), l’estimateur indique source: applicability_only ainsi qu’un skip_ratio: 0.0% conservateur.
Projections hypothétiques
Une projection ordinaire est estimée de la même manière, sur les parts que la requête lirait après élagage par clé primaire et par partition :
marks et rows indiquent le nombre de marques et de lignes que la lecture de la projection parcourrait elle-même, et non une fraction de la lecture de la table de base, car un granule de projection contient d’autres lignes qu’un granule de base. read_ratio rapporte ce nombre de marques à celui de la table de base : 0.02x correspond donc à cinquante fois moins de travail, et 15x à quinze fois plus. verdict applique la règle de l’optimiseur de requêtes : moins de marques que pour la table de base, ou autant lorsque la projection répond au ORDER BY de la requête. Un candidat peut donc être applicable sans être retenu.
EXPLAIN TABLE OVERRIDE
Affiche le résultat d’une surcharge de table appliquée au schéma d’une table accessible via une fonction de table. Effectue également certaines vérifications et lève une exception si la surcharge aurait entraîné un échec. Exemple Supposons que vous ayez une table MySQL distante comme celle-ci :Query
Query
Response
La validation n’est pas exhaustive ; une requête réussie ne garantit donc pas que la surcharge ne posera pas de problèmes.