Skip to main content
Mostra o plano de execução de uma instrução.
Sintaxe:
Exemplo:

Tipos de EXPLAIN

  • AST — Árvore de sintaxe abstrata.
  • SYNTAX — Texto da consulta após otimizações no nível da AST.
  • QUERY TREE — Árvore de consulta após otimizações no nível da árvore de consulta.
  • PLAN — Plano de execução da consulta.
  • PIPELINE — Pipeline de execução da consulta.
  • ANALYZE — Executa a consulta e anota o plano de execução com métricas de runtime medidas.
  • ESTIMATE — Número estimado de linhas, marcas e partes a serem lidas das tabelas durante o processamento da consulta.
  • TABLE OVERRIDE — Resultado validado de um override de tabela em um esquema de função de tabela.

EXPLAIN AST

Exibe a AST da consulta. Compatível com todos os tipos de consulta, não apenas SELECT. Configurações:
  • graph – Exibe a AST como um grafo descrito na linguagem de descrição de grafos DOT. Padrão: 0.
Exemplos:

EXPLAIN SYNTAX

Mostra a Árvore de Sintaxe Abstrata (AST) de uma consulta após a análise de sintaxe. Isso é feito fazendo o parsing da consulta, construindo a AST e a árvore de consulta da consulta, opcionalmente executando o analisador de consultas e os passes de otimização, e então convertendo a árvore de consulta de volta para a AST da consulta. Configurações:
  • oneline – Imprime a consulta em uma única linha. Padrão: 0.
  • run_query_tree_passes – Executa os passes da árvore de consulta antes de exibi-la. Padrão: 0.
  • query_tree_passes – Se run_query_tree_passes estiver definido, especifica quantos passes executar. Sem especificar query_tree_passes, ele executa todos os passes.
  • single_record – Retorna a consulta reformatada como um único registro multilinha em vez de um registro por linha. Padrão: 1 (controlado pela configuração explain_syntax_single_record). Defina como 0 para restaurar a saída histórica de um registro por linha, ou defina explain_syntax_single_record = 0 (globalmente ou em SETTINGS por consulta), ou defina compatibility como qualquer versão anterior à 26.8.
Exemplos:
Query
Response
Usando run_query_tree_passes:
Query
Response

EXPLAIN QUERY TREE

Configurações:
  • run_passes — Executa todos os passes da árvore de consulta antes de exibir a árvore de consulta. Padrão: 1.
  • dump_passes — Exibe informações sobre os passes usados antes de exibir a árvore de consulta. Padrão: 0.
  • passes — Especifica quantos passes devem ser executados. Se definido como -1, executa todos os passes. Padrão: -1.
  • dump_tree — Exibe a árvore de consulta. Padrão: 1.
  • dump_ast — Exibe a AST da consulta gerada a partir da árvore de consulta. Padrão: 0.
Exemplo:

EXPLAIN PLAN

Exibe os passos do plano de consulta. Configurações:
  • optimize — Controla se as otimizações do plano de consulta são aplicadas antes da exibição do plano. Padrão: 1.
  • header — Exibe o cabeçalho de saída do passo. Padrão: 0.
  • description — Exibe a descrição do passo. Padrão: 1.
  • indexes — Mostra os índices usados, o número de partes filtradas e o número de grânulos filtrados para cada índice aplicado. Padrão: 0. Compatível com tabelas MergeTree. A partir do ClickHouse >= v25.9, esta instrução só exibe uma saída adequada quando usada com SETTINGS use_query_condition_cache = 0, use_skip_indexes_on_data_read = 0.
  • projections — Mostra todas as projeções analisadas e seu efeito na filtragem no nível de partes com base nas condições da chave primária da projeção. Para cada projeção, esta seção inclui estatísticas como o número de partes, linhas, marcas e intervalos avaliados usando a chave primária da projeção. Também mostra quantas partes de dados foram ignoradas devido a essa filtragem, sem ler a própria projeção. Se uma projeção foi realmente usada para leitura ou apenas analisada para filtragem pode ser determinado pelo campo description. Padrão: 0. Compatível com tabelas MergeTree.
  • actions — Exibe informações detalhadas sobre as ações do passo. Padrão: 1.
  • sorting — Exibe a descrição da ordenação para cada passo do plano que produz saída ordenada. Padrão: 0.
  • keep_logical_steps — Mantém os passos lógicos do plano para junções em vez de convertê-los em implementações físicas de junção. Padrão: 0.
  • json — Exibe os passos do plano de consulta como uma linha no formato JSON. Padrão: 0. Recomenda-se usar o formato TabSeparatedRaw (TSVRaw) para evitar escapes desnecessários.
  • input_headers — Exibe os cabeçalhos de entrada do passo. Padrão: 0. Em geral, isso só é útil para desenvolvedores depurarem problemas relacionados à incompatibilidade entre cabeçalhos de entrada e saída.
  • column_structure — Exibe também a estrutura das colunas nos cabeçalhos, além do nome e do tipo. Padrão: 0. Em geral, isso só é útil para desenvolvedores depurarem problemas relacionados à incompatibilidade entre cabeçalhos de entrada e saída.
  • distributed — Mostra os planos de consulta executados em nós remotos para tabelas distribuídas ou réplicas paralelas. Não é compatível com json. Padrão: 0.
  • compact — Quando ativado, oculta do plano os passos de expressão e as informações detalhadas das ações (entradas, funções, aliases e posições de saída). Só tem efeito quando actions = 1. Padrão: 1.
  • pretty — Exibe a árvore do plano usando caracteres de desenho de linha (├──, └──, │) em vez de indentação para visualizar a hierarquia. Também formata as propriedades do passo de junção em linha. Padrão: 1.
Por padrão, explain_query_plan_default = 'pretty', portanto actions, compact e pretty são inicializados com 1, e o plano é renderizado na forma compacta, com formatação pretty e com anotações de ações. Especificar explicitamente qualquer uma dessas opções na instrução EXPLAIN (por exemplo, EXPLAIN actions = 0, compact = 0, pretty = 0 SELECT ...) sempre substitui o padrão.Antes do ClickHouse 26.7, os valores padrão de actions, compact e pretty eram 0. Você ainda pode obter essa saída definindo explain_query_plan_default = 'legacy' (globalmente ou em SETTINGS por consulta) ou definindo compatibility para qualquer versão anterior à 26.7.As opções json e distributed não habilitam os padrões de pretty (actions, compact e pretty), mesmo quando explain_query_plan_default = 'pretty'. Para incluir detalhes das ações na saída delas, defina actions = 1 manualmente.
Exemplo:
Não há suporte à estimativa de custo do passo e da consulta.
Quando json = 1, o plano de consulta é representado em formato JSON. Cada nó é um dicionário que sempre tem as chaves Node Type, Node Id e Plans. Node Type é uma string com o nome do passo, e Node Id é um identificador exclusivo do passo (o nome do passo com um sufixo numérico, por exemplo, Union_10). Plans é um array com descrições dos passos filhos. Outras chaves opcionais podem ser adicionadas dependendo do tipo de nó e das configurações. Exemplo:
Com description = 1, a chave Description é adicionada ao passo:
Com header = 1, a chave Header é adicionada ao passo na forma de um array de colunas. Exemplo:
Com indexes = 1, a chave Indexes é adicionada. Ela contém um array dos índices usados. Cada índice é descrito como JSON com a chave Type (uma string Partition Min-Max, Partition, Statistics, PrimaryKey ou Skip) e chaves opcionais: O índice Statistics usa estatísticas de coluna por parte (valores mínimo/máximo e o número de valores NULL para colunas Nullable) para pular partes que não podem corresponder ao filtro da consulta.
  • Name — O nome do índice (atualmente usado apenas para índices Skip).
  • Keys — O array de colunas usado pelo índice.
  • Condition — A condição usada.
  • Description — A descrição do índice (atualmente usada apenas para índices Skip).
  • Parts — O número de partes após/antes da aplicação do índice.
  • Granules — O número de grânulos após/antes da aplicação do índice.
  • Ranges — O número de intervalos de grânulos após a aplicação do índice.
Exemplo:
Com projections = 1, a chave Projections é adicionada. Ela contém um array de projeções analisadas. Cada projeção é descrita como JSON com as seguintes chaves:
  • Name — O nome da projeção.
  • Condition — A condição da chave primária usada pela projeção.
  • Description — A descrição de como a projeção é usada (por exemplo, filtragem em nível de partes).
  • Selected Parts — Número de partes selecionadas pela projeção.
  • Selected Marks — Número de marcas selecionadas.
  • Selected Ranges — Número de intervalos selecionados.
  • Selected Rows — Número de linhas selecionadas.
  • Filtered Parts — Número de partes ignoradas devido à filtragem em nível de partes.
Exemplo:
Com actions = 1, as chaves adicionadas dependem do tipo de passo. Exemplo:
Com compact = 0 e actions = 1, os passos de Expression podem ser vistos juntamente com informações detalhadas sobre as expressões:
Com distributed = 1, a saída inclui não apenas o plano de consulta local, mas também os planos de consulta que serão executados nos nós remotos. Isso é útil para analisar e depurar consultas distribuídas.
distributed é exibido apenas na forma legacy (sem pretty), porque a saída pretty não integra os planos de shard remotos à árvore do plano. Por esse motivo, habilitar distributed desabilita automaticamente os padrões de pretty (actions, compact e pretty), independentemente de explain_query_plan_default. Você ainda pode definir actions=1 manualmente. A opção distributed também não é compatível com json.
Exemplo com tabela distribuída:
Exemplo com réplicas paralelas:
Em ambos os exemplos, o plano de consulta exibe o fluxo de execução completo, incluindo etapas locais e remotas. Com pretty = 1, a árvore do plano é exibida usando caracteres de desenho de linhas em vez de recuo, e informações adicionais são mostradas para os passos principais:
  • As colunas de saída da consulta são exibidas no topo do plano.
  • Expressões em filtros, chaves de agregação, descrições de ordenação e funções de janela são exibidas em uma notação semelhante a SQL legível por humanos (por exemplo, a + 1 > 5 em vez de greater(plus(a, 1), 5)). Os prefixos internos de identificadores de coluna (como __table1.) são removidos para maior clareza.
  • Passos de origem (como ReadFromMergeTree) exibem suas colunas de saída.
  • Passos de filtro exibem a condição de filtro em notação SQL. Quando há filtros de join em tempo de execução, eles são mostrados separadamente.
  • Passos de agregação exibem chaves e funções de agregação com seus argumentos (por exemplo, sum(c), count()).
  • Conjuntos de IN de literais Tuple mostram seus valores (truncados para conjuntos grandes), conjuntos baseados em subconsulta são rotulados como subquery1, subquery2 etc., e conjuntos de tabelas com o motor Set mostram o nome da tabela.
  • Passos de join exibem a relação de join usando notação matemática, as estimativas que o otimizador de ordem de junções produziu para o passo (custo, seletividade, linhas de saída e linhas por lado), e as colunas de entrada de cada lado. Os símbolos a seguir são usados para representar diferentes tipos de join:
Por exemplo, t1 ⟕ t2 significa um left join entre as tabelas t1 e t2. O número entre colchetes após o nome da tabela (por exemplo, t1[100]) indica a contagem estimada de linhas quando estatísticas de tabela estão disponíveis. Abaixo da relação de join, cada passo de join exibe as estimativas que o otimizador de ordem de junções produziu para ele:
  • Cost — o custo de toda a subárvore de join abaixo desta etapa, que é o valor que o otimizador minimiza ao comparar ordens de join candidatas. O custo de um join é o número estimado de pares de linhas correspondentes, <selectivity> * <left_rows> * <right_rows>, mais o custo de suas entradas.
  • Selectivity — a fração estimada do produto cartesiano dos dois lados que permanece após a condição de join. Ela é derivada do número de valores distintos (NDV) das chaves de junção: uma igualdade de chave mantém cerca de 1 / max(NDV_left, NDV_right) dos pares, e é usada a menor fração entre as condições de join.
  • Output rows — o número estimado de linhas que o join produz: <selectivity> * <left_rows> * <right_rows> para um join interno, limitado inferiormente a <left_rows> para LEFT, a <right_rows> para RIGHT e a <left_rows> + <right_rows> para FULL, porque um join externo mantém todas as linhas do seu lado preservado. Quando um join SEMI ou ANTI participa da reordenação, ele é estimado como uma fração de seu lado preservado, <preserved_rows> * min(1, <selectivity> * <other_rows>) para SEMI e as linhas restantes para ANTI; caso contrário, usa a fórmula de seu tipo de join.
  • Left / Right — o número estimado de linhas que entram no join por cada lado.
Um valor que o otimizador não conseguiu estimar é exibido como no stats. Isso acontece quando a otimização da ordem de join não foi executada — por exemplo, quando query_plan_optimize_join_order_limit é 0 — ou quando não há base para a estimativa. As linhas Input (left): e Input (right): listam as colunas que cada lado fornece ao join. A opção pretty funciona bem em conjunto com compact = 1, que oculta os passos Expression e informações detalhadas das ações, tornando o plano mais fácil de ler. Um exemplo detalhado com junções. A configuração join_runtime_filter_min_probe_rows é reduzida apenas para que uma tabela tão pequena ainda crie um filtro de join em tempo de execução:

EXPLAIN PIPELINE

Configurações:
  • header — Imprime o cabeçalho de cada porta de saída. Padrão: 0.
  • graph — Imprime um grafo descrito na linguagem de descrição de grafos DOT. Padrão: 0.
  • compact — Imprime o grafo no modo compacto se a configuração graph estiver habilitada. Padrão: 1.
  • compact_repeated_processor_chains — Compacta cadeias repetidas adjacentes de processadores na saída de texto, mostrando uma cópia da cadeia com uma contagem de repetições. Isso pode facilitar a leitura de pipelines paralelos quando a mesma cadeia aparece muitas vezes, por exemplo, em junções. Isso não afeta a saída do grafo. Padrão: 0.
Quando compact=0 e graph=1, os nomes dos processadores conterão um sufixo adicional com um identificador exclusivo do processador. Exemplo:

EXPLAIN ANALYZE

EXPLAIN ANALYZE de fato executa a consulta, descarta as linhas de resultado e imprime a mesma árvore de plano que EXPLAIN PLAN, com cada passo anotado com o que realmente aconteceu em tempo de execução. Configurações: EXPLAIN ANALYZE aceita as mesmas opções de exibição que EXPLAIN PLAN (documentadas na seção EXPLAIN PLAN).
  • header — consulte a seção EXPLAIN PLAN.
  • description — consulte a seção EXPLAIN PLAN.
  • projections — consulte a seção EXPLAIN PLAN.
  • sorting — consulte a seção EXPLAIN PLAN.
  • input_headers — consulte a seção EXPLAIN PLAN.
  • column_structure — consulte a seção EXPLAIN PLAN.
  • actions — consulte a seção EXPLAIN PLAN. Padrão: 1.
  • indexes — consulte a seção EXPLAIN PLAN. Padrão: 1.
  • compact — consulte a seção EXPLAIN PLAN. Padrão: 1.
  • pretty — consulte a seção EXPLAIN PLAN. Padrão: 1.
  • processors — Para EXPLAIN ANALYZE, imprime uma linha adicional por estágio com a distribuição do tempo decorrido por processador: min, median, max e sum. Útil para identificar desequilíbrio de carga entre processadores paralelos. Padrão: 0.
  • matches — Para EXPLAIN ANALYZE, faz com que os passos de junção realizem o processamento adicional necessário para as métricas matched, match rate e fanout nos casos em que esses números não podem ser derivados do que a junção produz de qualquer forma. Quando podem, são relatados sem essa opção. Consulte Passos de junção. Padrão: 0.
  • time — Para EXPLAIN ANALYZE, imprime linhas Time e Concurrency por passo, com o tempo de relógio e o nível de concorrência do passo e de sua ramificação. Consulte Tempo de relógio e níveis de concorrência de passos e ramificações. Padrão: 1.
Como EXPLAIN ANALYZE realmente executa a consulta encapsulada, ele se comporta como essa consulta — e, diferentemente das formas EXPLAIN que não executam — de várias maneiras:
  • Cotas e limites. Ele é contabilizado nas mesmas quotas e está sujeito aos mesmos limits (por exemplo, query_selects, read_rows) que a execução direta da consulta. Fontes isentas de cotas durante o planejamento (como system.one) não são contabilizadas.
  • Transações com falha. Dentro de uma transação que já falhou (ROLLED_BACK), ele é rejeitado com INVALID_TRANSACTION, assim como uma instrução SELECT simples — emita ROLLBACK primeiro.
  • Leituras de streaming. Em uma leitura de streaming (FROM ... STREAM), ele é rejeitado com NOT_IMPLEMENTED, porque esse tipo de leitura nunca termina.
  • Consultas distribuídas. Não há suporte para ele em consultas executadas em modo Distributed.
Exemplo:
Vamos examinar a saída. Primeiro, vamos ver o cabeçalho.
  • Time — tempo total dividido entre as fases de planejamento (isto é, criação do plano + otimização do plano + construção do pipeline) e execução (execução do pipeline).
  • Execution — não é impresso quando time = 0. Divide o tempo de execução em in steps, outside steps e idle. Essas partes somam o valor de execution. Consulte Tempo de relógio e níveis de concorrência de passos e ramificações.
  • Read — linhas e bytes não comprimidos lidos das tabelas, com a taxa de transferência — os mesmos números que o rodapé padrão da consulta informa como “Processed”.
  • Peak memory — pico de memória usado pela consulta.
Agora vamos examinar as novas linhas que aparecem no plano da consulta.
Linhas e bytes são informados uma vez para todo o passo (a linha I/O). O tempo e o paralelismo são informados por estágio do passo nas linhas indentadas abaixo.
  • rows <in> → <out> — linhas que entraram e saíram do passo; (<selectivity>%) mostra o quanto o passo filtrou (out/in) ou expandiu os dados; fica oculto quando as linhas de entrada são iguais às linhas de saída e quando as linhas de entrada são iguais a 0.
  • <bytes_in> → <bytes_out> — bytes não comprimidos em memória que passam pelo passo (omitido quando ambos são zero).
  • time <t> (<share>%) — tempo de relógio em que o estágio esteve ativo e sua participação no tempo de execução da consulta (isto é, sem o tempo de compilação). Observe que as participações podem somar mais de 100%, porque estágios e passos são executados de forma concorrente.
  • parallelism <avg>/<max> — número médio de threads de CPU trabalhando ao mesmo tempo neste estágio, do máximo que ele poderia usar. Essa métrica se refere apenas ao estágio: threads que trabalham ao mesmo tempo em outros passos da consulta não são contabilizadas. Um valor próximo do máximo significa que o estágio foi bem paralelizado; próximo de 1 significa que ele foi executado principalmente de forma serial.
  • Stage (<stage>) — o nome do estágio. Um passo com um único estágio imprime a linha de tempo diretamente, sem um rótulo Stage (...). Passos com vários estágios imprimem uma linha identificada para cada estágio; por exemplo, Aggregating mostra Stage (partial aggregation) e Stage (final aggregation), e um hash join mostra Stage (build) e Stage (probe).
O ClickHouse paraleliza não apenas a execução de tarefas dentro de um passo do plano, mas também a execução dos próprios passos do plano. A métrica parallelism reflete apenas o trabalho deste passo. Outros passos podem ser executados de forma concorrente, portanto esse número não mostra como o paralelismo do passo se compara ao da consulta como um todo. Para ter uma visão da consulta como um todo, consulte a métrica Concurrency exibida com time = 1 (Tempo de relógio e níveis de concorrência dos passos e ramificações).
O número máximo em parallelism é calculado como o valor mínimo entre:
  1. o número total de tarefas dentro do passo do plano;
  2. o número máximo de threads de processamento de consultas definido em max_threads.

Passos de junção

Em um passo de junção, EXPLAIN ANALYZE exibe linhas que comparam as estimativas do otimizador de ordem de junção com o que realmente ocorreu (consulte Métricas de junção estimadas versus reais) e linhas de participação para cada lado — Left e Right — seguidas de linhas específicas da implementação da junção. Left e Right correspondem aos lados lógicos do SQL. Na maioria dos casos, Left também será o lado de sondagem (probe) da junção, e Right será o lado de construção (build). No entanto, isso nem sempre ocorre, por causa da troca que pode acontecer durante a execução da junção. Todos os valores de join_algorithm são contemplados (hash, parallel_hash, grace_hash, partial_merge, full_sorting_merge, parallel_full_sorting_merge, direct), assim como as três implementações que essa configuração não pode selecionar: uma junção CROSS ou COMMA, a junção de laço aninhado por blocos, usada quando a seção ON não determina nenhuma chave de junção, e o motor de tabela Join. A maioria deles informa ambos os lados; alguns informam apenas o lado que materializam (por exemplo, direct exibe apenas Left:). As linhas de cada lado seguem o mesmo formato:
Para cada lado, EXPLAIN ANALYZE informa:
  • rows estimated <estimated_rows> — a estimativa do otimizador de ordem de junção para as linhas desse lado, exibida para comparação com as rows reais ao lado; no stats quando o otimizador não produz uma estimativa (consulte Métricas de junção estimadas versus reais).
  • rows <rows> — o número total de linhas desse lado que passaram pela junção.
  • matched <matched_rows> — o número de linhas desse lado que encontraram pelo menos uma linha correspondente no outro lado. Isso conta linhas, não chaves: se uma chave ocorrer três vezes no lado direito e houver correspondência, as três linhas da direita serão contadas como correspondentes.
  • match rate <match_rate>% — a porcentagem de linhas desse lado que tiveram correspondência, calculada como 100 * <matched_rows> / <rows>.
  • fanout <fanout> — quantas linhas de saída, em média, cada linha correspondente desse lado produziu.
Um número que não pode ser calculado com exatidão é informado como not collected, em vez de 0. match rate e fanout são derivados de matched; portanto, um lado sem esse valor informa os três como not collected.

Métricas de join estimadas vs. reais

Uma etapa de junção traz as estimativas do otimizador de ordem de junção — as mesmas exibidas pelo EXPLAIN PLAN (consulte a seção EXPLAIN PLAN) — e o EXPLAIN ANALYZE imprime cada uma delas ao lado do valor medido:
  • Cost — ambos os valores contabilizam as linhas de saída correspondentes. A estimativa é o custo, para o otimizador, da subárvore da junção: <selectivity> * <left_rows> * <right_rows> mais o custo de suas entradas. O valor real é medido da mesma maneira — as linhas de saída correspondentes desta junção mais o custo real de cada junção abaixo dela que pertença ao mesmo cluster de reordenação.
  • Selectivity — a estimativa é derivada do número de valores distintos das chaves de junção; o valor real é a fração medida do produto cartesiano que chegou à saída: <matched output rows> / (<left rows> * <right rows>), pois é isso que o otimizador tenta estimar.
  • Output rows — o número estimado e real de linhas produzidas pela junção. Quando ambos são diferentes de zero, q-error informa max(estimated / actual, actual / estimated), a medida padrão da qualidade da estimativa de cardinalidade: 1.00 significa uma estimativa perfeita, e um valor alto significa que o otimizador escolheu a ordem das junções com base em uma cardinalidade muito imprecisa.
Uma estimativa que nunca foi feita é informada como no stats — por exemplo, quando a otimização da ordem das junções não foi executada porque query_plan_optimize_join_order_limit é 0. Isso é diferente de not collected, que indica um valor real que a execução não conseguiu medir. Junções com o lado direito pré-preenchido (o motor de tabela Join, junções direct) não passam pelo otimizador de ordem das junções e exibem apenas as linhas de participação. A etapa de junção também exibe as linhas Input (left): e Input (right): com as colunas que cada lado fornece à junção. Para as tabelas do exemplo de junção EXPLAIN PLAN, a etapa de junção de EXPLAIN ANALYZE SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id é assim:

Fanout

fanout mede a multiplicação de linhas:
Uma junção externa gera uma linha de saída preenchida com NULL para cada linha de um lado preservado que não encontrou correspondência. Essas linhas são subtraídas para não diluírem a razão. Apenas um lado preservado as tem — o lado direito em RIGHT e FULL, e o esquerdo em LEFT e FULL:
  • fanout = 0 — as linhas correspondentes não produziram nenhuma linha de saída, como ocorre em uma junção ANTI: ela gera apenas as linhas que não encontraram correspondência.
  • fanout = 1 — uma junção 1:1 sem duplicações; cada linha correspondente produziu exatamente uma linha de saída.
  • fanout > 1 — uma junção 1:N; chaves duplicadas do outro lado multiplicaram as linhas. Um valor alto nos dois lados ao mesmo tempo é sinal de uma explosão cartesiana não intencional.

Quando os números exigem matches = 1

A maioria desses números decorre dos dados que a junção já gera e é informada por um simples EXPLAIN ANALYZE. Os demais exigem uma contabilização que a junção não faria de outra forma e, por isso, só são informados com EXPLAIN ANALYZE matches = 1. Quais são eles depende do algoritmo; na família hash, há dois casos:
  • o lado direito de ALL INNER e ALL LEFT, que exige marcar todas as linhas correspondentes do lado direito;
  • o lado esquerdo de ALL LEFT e ALL FULL, mas somente quando a consulta não seleciona nada da tabela à direita e a seção ON é uma simples igualdade de chave. Caso contrário, a sondagem já registra quais linhas do lado esquerdo tiveram correspondência — seja para materializar as colunas do lado direito ou para avaliar a condição residual — e a contagem é exata sem a opção.
partial_merge precisa disso para o lado direito dos quatro tipos ALL, pelo mesmo motivo. full_sorting_merge e parallel_full_sorting_merge precisam disso para ambos os lados dos tipos ANY. Os tipos ALL não precisam de nada.
A opção fica desativada por padrão porque a contabilização adicional tem custo, e não usá-la reduz a fidelidade da medição. O trabalho é realizado dentro do loop de sondagem e aumenta com o número de linhas de saída. Use matches = 1 quando precisar saber as correspondências exatas encontradas pelas linhas dos lados esquerdo e direito.
matches = 1 não torna todas as combinações passíveis de coleta. Os lados que uma junção pode informar decorrem do que ela já precisa fazer de qualquer forma; portanto, isso depende tanto do algoritmo quanto do tipo e da strictness. Família hash. hash, parallel_hash e grace_hash sempre se comportam da mesma forma: O lado direito não está disponível sempre que a junção mantém apenas uma linha por chave em sua tabela hash, como fazem as junções ANY, SEMI e ANTI: as linhas duplicadas do lado direito nunca são armazenadas e, portanto, não podem ser contadas. O lado esquerdo não está disponível quando a junção suprime a saída de uma linha do lado esquerdo cuja correspondente já foi reivindicada por outra linha desse lado, fazendo com que as linhas emitidas sejam uma subcontagem das linhas com correspondência. Ativar any_join_distinct_right_table_keys muda ANY para a semântica mais antiga RightAny, que emite uma linha por linha do lado esquerdo e, portanto, mantém ambas as contagens. ANY RIGHT e ANY FULL passam a informar ambos os lados, e ANY INNER é reescrito como SEMI LEFT. O motor de tabela Join segue a mesma tabela, usando o tipo e a strictness declarados no mecanismo: Join(ALL, INNER, …) informa ambos os lados, enquanto Join(ANY, LEFT, …) não informa nenhum deles. Algoritmos de merge. full_sorting_merge e parallel_full_sorting_merge aceitam os quatro tipos ALL, ANY INNER, ANY LEFT, ANY RIGHT, ASOF e ASOF LEFT. Eles informam ambos os lados para todos os tipos, exceto ASOF e ASOF LEFT, nos quais o lado direito é not collected, mesmo sem matches = 1 — eles percorrem as duas entradas ordenadas e veem todas as linhas de uma faixa de valores iguais à medida que as consomem, portanto nada precisa ser reconstruído posteriormente. partial_merge aceita ALL INNER, ALL LEFT, ALL RIGHT, ALL FULL, ANY INNER, ANY LEFT e SEMI LEFT. Ele informa ambos os lados para os quatro tipos ALL, sendo o direito com matches = 1; para ANY INNER, ANY LEFT e SEMI LEFT, o lado direito é not collected. direct. Somente o lado esquerdo. O lado direito é um armazenamento de chave-valor que nunca é materializado em linhas e, portanto, não possui nenhuma linha Right:. CROSS, COMMA e um ON constante. Nenhum dos lados, conforme descrito acima. Junção de laço aninhado por blocos. Este operador recebe uma seção ON que não define nenhuma chave de junção, a menos que um dos caminhos anteriores a ele se encarregue da condição (consulte allow_block_nested_loop_join). Ele aceita todos os tipos e strictness, exceto ASOF, PASTE e ANY FULL: O lado esquerdo é informado sem essa opção sempre que o operador já registra quais linhas à esquerda tiveram correspondência: todo tipo que mantém as linhas à esquerda sem correspondência precisa desse registro, assim como toda strictness que aceita no máximo um par por linha à esquerda. O lado direito é informado sempre que o operador mantém seus indicadores de correspondência, necessários para RIGHT, FULL e SEMI/ANTI orientados pelo lado direito. ANY INNER também os mantém, mas apenas para atribuir uma linha à direita a uma única linha à esquerda: ele deixa de buscar correspondências para uma linha à esquerda que já tem seu par. Assim, os indicadores marcam os pares do resultado, e não todas as correspondências, e não são informados. ALL INNER só chega a esse operador quando hash está desabilitado, pois, caso contrário, a condição se torna um CROSS JOIN com um filtro. Habilitar any_join_distinct_right_table_keys faz com que ANY também use a semântica de RightAny neste caso, informando o lado esquerdo para todos os tipos e o lado direito para RIGHT e FULL; ANY INNER é reescrito como SEMI LEFT. Quando dois algoritmos informam um número, os valores coincidem. Os algoritmos de mesclagem apenas dispõem de mais informações; não divergem quanto ao que constitui uma correspondência.

Linhas específicas do algoritmo

Vejamos as linhas adicionais incluídas por cada implementação de junção. Para as junções hash e parallel_hash e para o motor de tabela Join, uma linha Hash table: descreve a tabela hash criada a partir da tabela à direita:
  • unique keys <unique_keys> — o número de chaves únicas armazenadas na tabela hash durante a fase de compilação.
  • memory <peak_memory> — o pico de memória usado pela tabela hash durante a fase de compilação.
Para a junção grace_hash, a linha Hash table: também informa como a junção se adaptou ao limite de memória, e uma linha Spill: informa se os dados foram gravados em disco:
  • buckets <buckets> — o número de buckets que a junção grace hash teve ao final da execução. Esse número é sempre uma potência de 2.
  • rehashes <rehashes> — quantas vezes o número de buckets precisou ser dobrado para se ajustar ao limite de memória.
  • Spill: — uma flag yes/no que indica se houve spill para disco. Quando houve, left spilled <left_spilled_bytes> e right spilled <right_spilled_bytes> informam os bytes comprimidos gravados em disco dos lados esquerdo (probe) e direito (build); quando não houve spill, a linha é simplesmente Spill: no.
Na junção partial_merge, a linha Right: contém informações adicionais sobre como a tabela da direita foi armazenada em buffer e ordenada, e o tempo de ordenação é exibido nas linhas Stage (build) e Stage (probe):
  • size <right_size> — o tamanho em memória dos blocos da tabela à direita.
  • blocks <right_blocks> — o número de blocos nos quais a tabela à direita foi armazenada em buffer.
  • storage <in-memory|external> — indica se a tabela à direita coube na memória (in-memory) ou precisou ser descarregada em disco (external). Quando é external, um campo adicional spilled <spilled_bytes> informa os bytes comprimidos gravados em disco.
  • sort time <sort_time> — o tempo gasto na ordenação da tabela à direita (na etapa de compilação) e de cada bloco recebido da esquerda (na etapa de sondagem).
  • sort share <sort_share>% — sort time como proporção do tempo de ocupação da própria etapa (a soma do tempo decorrido de seus processadores), diferentemente da porcentagem de time da etapa, que representa uma proporção do tempo de execução de toda a consulta.
Para a junção full_sorting_merge, são impressas apenas as linhas comuns Left: e Right:. Para a junção direct, é impressa apenas a linha Left:, pois o lado direito é um armazenamento de chave-valor consultado diretamente, em vez de ser materializado em linhas. Para uma junção CROSS ou COMMA, e para um ALL INNER JOIN cuja seção ON não tem igualdade de chave — que o planejador converte em um CROSS JOIN com essa condição como filtro —, uma linha Buffer: descreve como a tabela à direita foi mantida na memória, e uma linha Spill: informa se ela foi descarregada em disco:
  • memory <peak_memory> — o pico de memória ocupado pela tabela da direita armazenada em buffer.
  • compressed <yes|no> — indica se pelo menos um bloco armazenado em buffer foi comprimido; nesse caso, os leitores descomprimem todos os blocos armazenados.
  • Spill: — a mesma flag yes/no de grace_hash, com right spilled <right_spilled_bytes> informando os bytes comprimidos gravados em disco.
Ambos os lados informam matched not collected aqui: um predicado constante associa todas as linhas da esquerda a todas as linhas da direita ou a nenhuma delas; portanto, não é possível determinar quais linhas individuais corresponderam. A junção de laço aninhado por blocos, que processa todas as demais seções ON sem igualdade de chave, exibe as mesmas duas linhas sobre a tabela da direita que ela materializa:
  • memory <peak_memory> — o pico de memória ocupado pela tabela direita armazenada. É 0 quando cada bloco foi gravado em disco assim que chegou, já que nenhum deles chegou a ser mantido em memória.
  • compressed <yes|no> — indica se pelo menos um bloco armazenado foi comprimido; nesse caso, os leitores descomprimem todos os blocos armazenados.
  • Spill: — o mesmo indicador yes/no usado em grace_hash, com right spilled <right_spilled_bytes> informando os bytes comprimidos gravados em disco.
Ao contrário do caso CROSS, aqui ambos os lados podem informar matched, já que o operador avalia uma condição real em cada par que examina; a tabela acima indica qual deles o faz. Para uma junção com o motor de tabela Join, ambos os lados são informados, juntamente com a linha Hash table:, que descreve a tabela pré-criada. O lado direito contabiliza as linhas armazenadas no motor, não as linhas de uma compilação específica da consulta.

Tempos por processador

Com processors = 1, uma linha extra é impressa abaixo de cada estágio, mostrando a distribuição do tempo decorrido entre os processadores do estágio:
<n> é o número de processadores no estágio. Uma grande diferença entre median e max indica um desequilíbrio de carga entre processadores paralelos.

Tempo decorrido (wall-clock) e níveis de concorrência de passos e ramificações

Exceto quando time = 0, duas linhas adicionais são exibidas para cada passo do plano de consulta:
Aqui, step refere-se ao próprio passo. branch refere-se à subárvore cuja raiz é o passo: o passo e todos os passos abaixo dele no plano.
  • Time — o tempo de relógio durante o qual pelo menos uma thread executou trabalho para o passo (step) ou para qualquer passo da subárvore (branch). <share> é esse tempo como porcentagem do tempo de execução da consulta.
  • Concurrency — o número médio de threads ocupadas com qualquer passo da consulta, e não apenas com este, enquanto o passo (step) ou sua subárvore (branch) estava ativo. <m> é o número máximo de threads que a consulta poderia usar. Passos que não possuem processadores, como Union quando suas entradas já têm o mesmo cabeçalho, exibem Unknown para step: não há trabalho a medir. O valor de branch continua sendo reportado.
Ambas as métricas são derivadas de intervalos de trabalho. Sempre que uma thread conclui uma parte do trabalho, o executor registra um intervalo: o instante de início, o instante de término e o passo ao qual o trabalho pertence. O Time de um passo é o comprimento da união dos intervalos desse passo. Lacunas, em que nenhuma thread trabalhou no passo, não são contabilizadas. No caso de um branch, o Time é calculado unindo primeiro os intervalos de todos os passos da subárvore. O branch do passo raiz corresponde ao plano inteiro, mas sua participação normalmente fica abaixo de 100%. A linha Execution do resumo da consulta mostra para onde foi o restante do tempo de execução (veja o resumo da consulta no exemplo acima):
  • in steps — a união dos intervalos de todos os passos do plano. Corresponde ao tempo de branch do passo raiz, com a mesma participação.
  • outside steps — o tempo em que pelo menos uma thread executou um processador, mas nenhum deles pertencia a um passo deste plano: o sink de saída, Resize, transformações de conversão e os passos de planos que não são alcançáveis a partir deste.
  • idle — o tempo em que nenhuma thread executou qualquer processador: a inicialização e o desligamento do executor, além dos momentos em que todas as threads estavam aguardando.
As participações de Time, as participações de time por etapa e as participações de Execution usam o mesmo denominador e, portanto, podem ser comparadas entre si. Concurrency é o tempo total de ocupação de todas as threads da consulta dentro dessa união, dividido pelo comprimento da união. Ela contabiliza as threads ocupadas com qualquer passo da consulta, ao contrário da métrica parallelism por etapa, que contabiliza apenas as threads da própria etapa. Ela responde à pergunta: “enquanto este passo (branch) estava ativo, quão ocupada estava a consulta como um todo?” Um valor próximo de <m> significa que a consulta permaneceu totalmente paralela durante o ciclo de vida desse passo. Um valor próximo de 1 significa que a consulta foi serializada durante esse período: para step, a única thread ocupada estava trabalhando no próprio passo, o que o torna um ponto de serialização; para branch, a thread estava trabalhando em algum ponto da subárvore, não necessariamente neste passo. Um passo serializado só representa um gargalo real quando sua participação em Time também é significativa.

EXPLAIN ESTIMATE

Mostra o número estimado de linhas, marcas e partes que serão lidas das tabelas durante o processamento da consulta. Funciona com tabelas da família MergeTree. Exemplo Criando uma tabela:
Query
Query
Response

EXPLAIN WHATIF

Estima o benefício que um índice de exclusão hipotético traria para uma consulta SELECT, sem materializar o índice em disco. Defina um ou mais candidatos com CREATE HYPOTHETICAL INDEX e execute EXPLAIN WHATIF SELECT ... para ver a aplicabilidade, a estimativa de marcas lidas, a estimativa de bytes e a taxa de descarte de cada candidato. Projeções hipotéticas definidas com CREATE HYPOTHETICAL PROJECTION também são candidatas. Para estimar uma projeção normal, seu índice primário é construído em memória sobre as partes que a consulta leria e, em seguida, é podado como seria o de uma projeção materializada. O relatório mostra as marcas e linhas que a projeção leria, um read_ratio em relação à leitura da tabela base e um verdict indicando se o otimizador a escolheria. Com force_optimize_projection = 1 ou prefer_optimize_projection = 1, o otimizador usa qualquer projeção utilizável; assim, uma projeção que ele não escolheria pelo custo é informada como chosen (forced). Quando o resultado depende de como a parte da projeção seria organizada, marks_span apresenta o intervalo, e uma decisão que poderia pender para qualquer lado é informada como too close to call. Em tabelas grandes, a estimativa lê uma amostra de grânulos (consulte projection_scan_budget_rows). Projeções agregadas, projeções com uma cláusula WHERE, com índices de exclusão próprios, com chave baseada na ordem de commit ou em coluna virtual, ou que armazenam _block_number, e projeções que não cobrem todas as colunas lidas pela consulta são informadas como not_applicable. force_optimize_projection_name e preferred_optimize_projection_name são ignorados, e force_optimize_projection não faz a instrução falhar. Sintaxe
Configurações
  • projection_scan_budget_rows — quantas linhas uma estimativa de projeção pode ler antes de recorrer a uma amostra de grânulos. Padrão: 10000000. 0 significa sem limite; max_rows_to_read pode reduzi-lo. Uma amostra lê pelo menos cerca de 30 grânulos, além de um por parte. Quando max_rows_to_read não permite isso, a estimativa fica como unsupported.
  • empirical — 1 (padrão) aplica o índice em memória aos grânulos restantes após o descarte inicial para medir a taxa de descarte (um limite superior). 0 ignora essa etapa. Em ambos os casos, se empirical não produzir um resultado (por estar desabilitado ou porque o índice não pode ser avaliado em memória), o estimador recorre às estatísticas da coluna e, se elas também não estiverem disponíveis, apresenta apenas um resumo da aplicabilidade.
Saída
  • source — como a estimativa foi gerada.
    • empirical: construiu o índice em memória sobre os grânulos remanescentes após a poda de referência e contou os grânulos que o índice pularia. Este é um limite superior — veja as limitações em CREATE HYPOTHETICAL INDEX.
    • statistical: derivado de estatísticas de coluna. Usado quando a estimativa empírica está desabilitada (empirical = 0) ou não conseguiu produzir um resultado, e há estatísticas de coluna definidas nas colunas relevantes.
    • applicability_only: o índice é aplicável ao predicado, mas nem a estimativa empírica nem a estatística produziram um resultado (por exemplo, empirical = 0 e nenhuma estatística de coluna definida). Informa skip_ratio: 0.0% como um limite conservador.
  • empirical_reason — por que a estimativa empírica não pôde ser executada. Exibido apenas com empirical_status: unsupported. Por exemplo, um valor diferente de zero para merge_tree_min_rows_for_seek ou merge_tree_min_bytes_for_seek faz com que uma leitura real agrupe intervalos de marcas, algo que a contagem por grânulo não modela; portanto, a estimativa recorre a statistical ou applicability_only.
  • sampled_parts / sampled_marks — <baseline-pruned> / <total in the table>. Mostra que fração da tabela restou após a poda por PK, partição e índices existentes, ou seja, a entrada para o índice hipotético. Para uma projeção, sampled_marks é <marks read> / <marks in the parts the query reads>, e um primeiro número menor que o segundo significa que a estimativa usou uma amostra.
  • est_bytes — uma estimativa dos bytes lidos, derivada do tamanho médio de linha da tabela; por isso, é aproximada e varia conforme o armazenamento e a compressão. A linha de referência aparece apenas quando a consulta lê linhas; a linha de cada candidato, apenas quando a estimativa de bytes da linha de referência é conhecida.
A configuração é escrita inline entre WHATIF e SELECT — não há palavra-chave SETTINGS (isso corresponde à forma como outras variantes de EXPLAIN aceitam suas opções). Se nem índices hipotéticos nem projeções hipotéticas estiverem definidos para a tabela, EXPLAIN WHATIF informa status: not_applicable com uma dica para criar um. Linha combinada (múltiplos candidatos) Quando dois ou mais candidatos são avaliados empiricamente, EXPLAIN WHATIF acrescenta um bloco chamado (combined: idx_a, idx_b, ...) após as linhas de cada candidato. Ele informa o benefício conjunto de ter todos esses índices ao mesmo tempo: em uma leitura real, um grânulo só é mantido se passar por todos os índices de exclusão; portanto, a estimativa combinada corresponde à interseção dos grânulos remanescentes de cada candidato. Assim, seu skip_ratio é pelo menos tão alto quanto o do melhor candidato individual — índices complementares descartam mais grânulos em conjunto, enquanto índices redundantes não alteram o resultado. Apenas candidatos com source: empirical contribuem, pois a linha combinada é construída pela interseção dos conjuntos de grânulos remanescentes de cada candidato. Candidatos estimados como statistical ou applicability_only não têm dados por grânulo e são excluídos; por isso, o bloco combinado aparece somente quando pelo menos dois candidatos produziram uma estimativa empírica. Caso contrário, ele é omitido (por exemplo, com empirical = 0). Seus campos de estimativa têm o mesmo significado que os de um bloco empírico de um candidato individual, exceto elapsed_us, que é 0 — a estimativa combinada é derivada das varreduras de cada candidato, não de uma nova varredura. O nome sintético (combined: ...) é apenas um rótulo no relatório e não pode ser usado com force_data_skipping_indices. Exemplo empírico
O minmax hipotético reduziria o número de marcas de 100 para 1 — skip_ratio: 99.0%. (est_bytes é uma estimativa baseada no tamanho médio das linhas, por isso o valor exato varia.) Exemplo estatístico As estatísticas de coluna vêm desativadas por padrão. Para usar o caminho statistical, primeiro defina-as nas colunas relevantes e aguarde a conclusão da mutação de materialização:
Em seguida, desative o caminho empírico para que o estimador passe a usar as estatísticas de coluna:
O número vem da seletividade das estatísticas da coluna de b < 10 (cerca de 10 linhas em 10000) e é informado como um limite superior de skip_ratio. Não há sampled_parts / sampled_marks — nenhum dado foi lido. Se nenhum dos dois caminhos estiver disponível (por exemplo, empirical = 0 e nenhuma estatística de coluna definida), o estimador informa source: applicability_only e um skip_ratio: 0.0% conservador. Projeções hipotéticas Uma projeção normal é estimada da mesma forma, com base nas partes que a consulta leria após a poda por chave primária e por partição:
marks e rows indicam o que a leitura da própria projeção acessaria, não uma parcela da leitura da tabela base, pois um grânulo da projeção contém linhas diferentes das de um grânulo da tabela base. read_ratio é a razão entre essas marcas e as marcas da tabela base: 0.02x significa cinquenta vezes menos trabalho, enquanto 15x significa quinze vezes mais. verdict segue a regra do próprio otimizador — menos marcas do que na tabela base, ou o mesmo número quando a projeção atende ao ORDER BY da consulta —, por isso um candidato pode ser applicable e, ainda assim, não ser escolhido.

EXPLAIN TABLE OVERRIDE

Mostra o resultado de um override de tabela em um esquema acessado por meio de uma função de tabela. Também faz algumas validações, gerando uma exceção se o override causar algum tipo de falha. Exemplo Suponha que você tenha uma tabela MySQL remota como esta:
Query
Query
Response
A validação não está completa, portanto uma consulta bem-sucedida não garante que o override não cause problemas.