> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Comprender la ejecución de las consultas con el analizador

> Describe cómo usar el analizador para comprender cómo ClickHouse ejecuta las consultas

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

ClickHouse procesa las consultas con gran rapidez, pero la ejecución de una consulta no es tan sencilla. Intentemos entender cómo se ejecuta una consulta `SELECT`. Para ilustrarlo, agreguemos algunos datos a una tabla de ClickHouse:

```sql theme={null}
CREATE TABLE session_events(
   clientId UUID,
   sessionId UUID,
   pageId UUID,
   timestamp DateTime,
   type String
) ORDER BY (timestamp);

INSERT INTO session_events SELECT * FROM generateRandom('clientId UUID,
   sessionId UUID,
   pageId UUID,
   timestamp DateTime,
   type Enum(\'type1\', \'type2\')', 1, 10, 2) LIMIT 1000;
```

Ahora que tenemos algunos datos en ClickHouse, queremos ejecutar algunas consultas y comprender cómo se ejecutan. La ejecución de una consulta se descompone en muchos pasos. Cada paso de la ejecución de la consulta puede analizarse y depurarse mediante la consulta `EXPLAIN` correspondiente. Estos pasos se resumen en el siguiente gráfico:

<Image img="https://mintcdn.com/private-7c7dfe99/k4wNHsd_gyvah7Fr/images/guides/developer/analyzer1.webp?fit=max&auto=format&n=k4wNHsd_gyvah7Fr&q=85&s=be70a40e52826e270c3ce95ad5f0b20b" alt="Pasos de la consulta EXPLAIN" size="md" width="2048" height="960" data-path="images/guides/developer/analyzer1.webp" />

Veamos cada elemento en acción durante la ejecución de la consulta. Vamos a tomar algunas consultas y luego examinarlas mediante la sentencia `EXPLAIN`.

<div id="parser">
  ## Analizador sintáctico
</div>

El objetivo de un analizador sintáctico es transformar el texto de la consulta en un AST (árbol de sintaxis abstracta). Este paso se puede visualizar con `EXPLAIN AST`:

```sql theme={null}
EXPLAIN AST SELECT min(timestamp), max(timestamp) FROM session_events;
```

```response theme={null}
┌─explain────────────────────────────────────────────┐
│ SelectWithUnionQuery (children 1)                  │
│  ExpressionList (children 1)                       │
│   SelectQuery (children 2)                         │
│    ExpressionList (children 2)                     │
│     Function min (alias minimum_date) (children 1) │
│      ExpressionList (children 1)                   │
│       Identifier timestamp                         │
│     Function max (alias maximum_date) (children 1) │
│      ExpressionList (children 1)                   │
│       Identifier timestamp                         │
│    TablesInSelectQuery (children 1)                │
│     TablesInSelectQueryElement (children 1)        │
│      TableExpression (children 1)                  │
│       TableIdentifier session_events               │
└────────────────────────────────────────────────────┘
```

La salida es un árbol de sintaxis abstracta que puede visualizarse como se muestra a continuación:

<Image img="https://mintcdn.com/private-7c7dfe99/k4wNHsd_gyvah7Fr/images/guides/developer/analyzer2.webp?fit=max&auto=format&n=k4wNHsd_gyvah7Fr&q=85&s=8d5b4b160142f724bca6da94ed272992" alt="Salida del AST" size="md" width="2048" height="1058" data-path="images/guides/developer/analyzer2.webp" />

Cada nodo tiene sus nodos hijo correspondientes, y el árbol en su conjunto representa la estructura general de tu consulta. Se trata de una estructura lógica que ayuda a procesar una consulta. Desde el punto de vista del usuario final (salvo que le interese la ejecución de consultas), no resulta especialmente útil; esta herramienta está pensada principalmente para desarrolladores.

<div id="analyzer">
  ## Analizador
</div>

Actualmente, ClickHouse tiene dos arquitecturas para el Analizador. Puede usar la arquitectura anterior configurando: `enable_analyzer=0`. La arquitectura actual está habilitada de forma predeterminada desde ClickHouse `24.3`. Aquí describiremos solo la arquitectura actual, dado que la anterior está obsoleta y se conserva solo por compatibilidad con versiones anteriores.

<Note>
  La arquitectura actual debería proporcionarnos un mejor marco para mejorar el rendimiento de ClickHouse. Sin embargo, dado que es un componente fundamental en las etapas del procesamiento de consultas, también podría tener un impacto negativo en algunas consultas y existen [incompatibilidades conocidas](/docs/es/guides/clickhouse/performance-and-monitoring/analyzer#known-incompatibilities). Puede volver a la arquitectura anterior cambiando la configuración `enable_analyzer` a nivel de consulta o de usuario.
</Note>

El analizador es una etapa importante de la ejecución de consultas. Toma un AST y lo transforma en un árbol de consulta. La principal ventaja de un árbol de consulta frente a un AST es que muchos componentes quedan resueltos, como el almacenamiento, por ejemplo. También sabemos de qué tabla leer, los alias se resuelven y el árbol conoce los distintos tipos de datos utilizados. Gracias a todo ello, el analizador puede aplicar optimizaciones. Estas optimizaciones funcionan mediante "pasadas". Cada pasada busca optimizaciones diferentes. Puede ver todas las pasadas [aquí](https://github.com/ClickHouse/ClickHouse/blob/76578ebf92af3be917cd2e0e17fea2965716d958/src/Analyzer/QueryTreePassManager.cpp#L249); veámoslo en la práctica con nuestra consulta anterior:

```sql theme={null}
EXPLAIN QUERY TREE passes=0 SELECT min(timestamp) AS minimum_date, max(timestamp) AS maximum_date FROM session_events SETTINGS allow_experimental_analyzer=1;
```

```response theme={null}
┌─explain────────────────────────────────────────────────────────────────────────────────┐
│ QUERY id: 0                                                                            │
│   PROJECTION                                                                           │
│     LIST id: 1, nodes: 2                                                               │
│       FUNCTION id: 2, alias: minimum_date, function_name: min, function_type: ordinary │
│         ARGUMENTS                                                                      │
│           LIST id: 3, nodes: 1                                                         │
│             IDENTIFIER id: 4, identifier: timestamp                                    │
│       FUNCTION id: 5, alias: maximum_date, function_name: max, function_type: ordinary │
│         ARGUMENTS                                                                      │
│           LIST id: 6, nodes: 1                                                         │
│             IDENTIFIER id: 7, identifier: timestamp                                    │
│   JOIN TREE                                                                            │
│     IDENTIFIER id: 8, identifier: session_events                                       │
│   SETTINGS allow_experimental_analyzer=1                                               │
└────────────────────────────────────────────────────────────────────────────────────────┘
```

```sql theme={null}
EXPLAIN QUERY TREE passes=20 SELECT min(timestamp) AS minimum_date, max(timestamp) AS maximum_date FROM session_events SETTINGS allow_experimental_analyzer=1;
```

```response theme={null}
┌─explain───────────────────────────────────────────────────────────────────────────────────┐
│ QUERY id: 0                                                                               │
│   PROJECTION COLUMNS                                                                      │
│     minimum_date DateTime                                                                 │
│     maximum_date DateTime                                                                 │
│   PROJECTION                                                                              │
│     LIST id: 1, nodes: 2                                                                  │
│       FUNCTION id: 2, function_name: min, function_type: aggregate, result_type: DateTime │
│         ARGUMENTS                                                                         │
│           LIST id: 3, nodes: 1                                                            │
│             COLUMN id: 4, column_name: timestamp, result_type: DateTime, source_id: 5     │
│       FUNCTION id: 6, function_name: max, function_type: aggregate, result_type: DateTime │
│         ARGUMENTS                                                                         │
│           LIST id: 7, nodes: 1                                                            │
│             COLUMN id: 4, column_name: timestamp, result_type: DateTime, source_id: 5     │
│   JOIN TREE                                                                               │
│     TABLE id: 5, alias: __table1, table_name: default.session_events                      │
│   SETTINGS allow_experimental_analyzer=1                                                  │
└───────────────────────────────────────────────────────────────────────────────────────────┘
```

Entre ambas ejecuciones, se puede ver la resolución de alias y proyecciones.

<div id="planner">
  ## Planificador
</div>

El planificador toma un árbol de consulta y, a partir de él, construye un plan de consulta. El árbol de consulta nos indica qué queremos hacer con una consulta concreta, y el plan de consulta nos indica cómo lo haremos. También se realizan optimizaciones adicionales como parte del plan de consulta. Puede usar `EXPLAIN PLAN` o `EXPLAIN` para ver el plan de consulta (`EXPLAIN` ejecutará `EXPLAIN PLAN`).

```sql theme={null}
EXPLAIN PLAN WITH
   (
       SELECT count(*)
       FROM session_events
   ) AS total_rows
SELECT type, min(timestamp) AS minimum_date, max(timestamp) AS maximum_date, count(*) /total_rows * 100 AS percentage FROM session_events GROUP BY type
```

```response theme={null}
┌─explain──────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY))      │
│   Aggregating                                    │
│     Expression (Before GROUP BY)                 │
│       ReadFromMergeTree (default.session_events) │
└──────────────────────────────────────────────────┘
```

Aunque esto nos da cierta información, podemos obtener más. Por ejemplo, quizá queramos conocer el nombre de la columna sobre la que necesitamos las proyecciones. Puedes añadir la cabecera a la consulta:

```SQL theme={null}
EXPLAIN header = 1
WITH (
       SELECT count(*)
       FROM session_events
   ) AS total_rows
SELECT
   type,
   min(timestamp) AS minimum_date,
   max(timestamp) AS maximum_date,
   (count(*) / total_rows) * 100 AS percentage
FROM session_events
GROUP BY type
```

```response theme={null}
┌─explain──────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY))      │
│ Header: type String                              │
│         minimum_date DateTime                    │
│         maximum_date DateTime                    │
│         percentage Nullable(Float64)             │
│   Aggregating                                    │
│   Header: type String                            │
│           min(timestamp) DateTime                │
│           max(timestamp) DateTime                │
│           count() UInt64                         │
│     Expression (Before GROUP BY)                 │
│     Header: timestamp DateTime                   │
│             type String                          │
│       ReadFromMergeTree (default.session_events) │
│       Header: timestamp DateTime                 │
│               type String                        │
└──────────────────────────────────────────────────┘
```

Así que ahora ya conoces los nombres de las columnas que deben crearse para la última Projection (`minimum_date`, `maximum_date` y `percentage`), pero quizá también quieras ver los detalles de todas las acciones que deben ejecutarse. Puedes hacerlo estableciendo `actions=1`.

```sql theme={null}
EXPLAIN actions = 1
WITH (
       SELECT count(*)
       FROM session_events
   ) AS total_rows
SELECT
   type,
   min(timestamp) AS minimum_date,
   max(timestamp) AS maximum_date,
   (count(*) / total_rows) * 100 AS percentage
FROM session_events
GROUP BY type
```

```response theme={null}
┌─explain────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY))                                                                                                │
│ Actions: INPUT :: 0 -> type String : 0                                                                                                     │
│          INPUT : 1 -> min(timestamp) DateTime : 1                                                                                          │
│          INPUT : 2 -> max(timestamp) DateTime : 2                                                                                          │
│          INPUT : 3 -> count() UInt64 : 3                                                                                                   │
│          COLUMN Const(Nullable(UInt64)) -> total_rows Nullable(UInt64) : 4                                                                 │
│          COLUMN Const(UInt8) -> 100 UInt8 : 5                                                                                              │
│          ALIAS min(timestamp) :: 1 -> minimum_date DateTime : 6                                                                            │
│          ALIAS max(timestamp) :: 2 -> maximum_date DateTime : 1                                                                            │
│          FUNCTION divide(count() :: 3, total_rows :: 4) -> divide(count(), total_rows) Nullable(Float64) : 2                               │
│          FUNCTION multiply(divide(count(), total_rows) :: 2, 100 :: 5) -> multiply(divide(count(), total_rows), 100) Nullable(Float64) : 4 │
│          ALIAS multiply(divide(count(), total_rows), 100) :: 4 -> percentage Nullable(Float64) : 5                                         │
│ Positions: 0 6 1 5                                                                                                                         │
│   Aggregating                                                                                                                              │
│   Keys: type                                                                                                                               │
│   Aggregates:                                                                                                                              │
│       min(timestamp)                                                                                                                       │
│         Function: min(DateTime) → DateTime                                                                                                 │
│         Arguments: timestamp                                                                                                               │
│       max(timestamp)                                                                                                                       │
│         Function: max(DateTime) → DateTime                                                                                                 │
│         Arguments: timestamp                                                                                                               │
│       count()                                                                                                                              │
│         Function: count() → UInt64                                                                                                         │
│         Arguments: none                                                                                                                    │
│   Skip merging: 0                                                                                                                          │
│     Expression (Before GROUP BY)                                                                                                           │
│     Actions: INPUT :: 0 -> timestamp DateTime : 0                                                                                          │
│              INPUT :: 1 -> type String : 1                                                                                                 │
│     Positions: 0 1                                                                                                                         │
│       ReadFromMergeTree (default.session_events)                                                                                           │
│       ReadType: Default                                                                                                                    │
│       Parts: 1                                                                                                                             │
│       Granules: 1                                                                                                                          │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

Ahora puedes ver todas las entradas, funciones, alias y tipos de datos que se están usando. Puedes consultar [aquí](https://github.com/ClickHouse/ClickHouse/blob/master/src/Processors/QueryPlan/Optimizations/Optimizations.h) algunas de las optimizaciones que el planificador va a aplicar.

<div id="query-pipeline">
  ## Pipeline de consulta
</div>

El pipeline de consulta se genera a partir del plan de consulta. El pipeline de consulta es muy similar al plan de consulta, con la diferencia de que no es un árbol sino un grafo. Ilustra cómo ClickHouse va a ejecutar una consulta y qué recursos se van a utilizar. Analizar el pipeline de consulta resulta muy útil para identificar dónde se encuentra el cuello de botella en términos de entradas/salidas. Tomemos nuestra consulta anterior y examinemos la ejecución del pipeline de consulta:

```sql theme={null}
EXPLAIN PIPELINE
WITH (
       SELECT count(*)
       FROM session_events
   ) AS total_rows
SELECT
   type,
   min(timestamp) AS minimum_date,
   max(timestamp) AS maximum_date,
   (count(*) / total_rows) * 100 AS percentage
FROM session_events
GROUP BY type;
```

```response theme={null}
┌─explain────────────────────────────────────────────────────────────────────┐
│ (Expression)                                                               │
│ ExpressionTransform × 2                                                    │
│   (Aggregating)                                                            │
│   Resize 1 → 2                                                             │
│     AggregatingTransform                                                   │
│       (Expression)                                                         │
│       ExpressionTransform                                                  │
│         (ReadFromMergeTree)                                                │
│         MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) 0 → 1 │
└────────────────────────────────────────────────────────────────────────────┘
```

Dentro del paréntesis se encuentra el paso del plan de consulta y, junto a él, el procesador. Es información muy útil, pero dado que se trata de un grafo, sería conveniente visualizarlo como tal. Contamos con un parámetro `graph` que podemos establecer en 1 y especificar el formato de salida como TSV:

```sql theme={null}
EXPLAIN PIPELINE graph=1 WITH
   (
       SELECT count(*)
       FROM session_events
   ) AS total_rows
SELECT type, min(timestamp) AS minimum_date, max(timestamp) AS maximum_date, count(*) /total_rows * 100 AS percentage FROM session_events GROUP BY type FORMAT TSV;
```

```response theme={null}
digraph
{
 rankdir="LR";
 { node [shape = rect]
   subgraph cluster_0 {
     label ="Expression";
     style=filled;
     color=lightgrey;
     node [style=filled,color=white];
     { rank = same;
       n5 [label="ExpressionTransform × 2"];
     }
   }
   subgraph cluster_1 {
     label ="Aggregating";
     style=filled;
     color=lightgrey;
     node [style=filled,color=white];
     { rank = same;
       n3 [label="AggregatingTransform"];
       n4 [label="Resize"];
     }
   }
   subgraph cluster_2 {
     label ="Expression";
     style=filled;
     color=lightgrey;
     node [style=filled,color=white];
     { rank = same;
       n2 [label="ExpressionTransform"];
     }
   }
   subgraph cluster_3 {
     label ="ReadFromMergeTree";
     style=filled;
     color=lightgrey;
     node [style=filled,color=white];
     { rank = same;
       n1 [label="MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread)"];
     }
   }
 }
 n3 -> n4 [label=""];
 n4 -> n5 [label="× 2"];
 n2 -> n3 [label=""];
 n1 -> n2 [label=""];
}
```

A continuación, puede copiar esta salida y pegarla [aquí](https://dreampuf.github.io/GraphvizOnline) para generar el siguiente grafo:

<Image img="https://mintcdn.com/private-7c7dfe99/k4wNHsd_gyvah7Fr/images/guides/developer/analyzer3.webp?fit=max&auto=format&n=k4wNHsd_gyvah7Fr&q=85&s=6d38ed6b125416a1354642c6bb767b86" alt="Resultado del grafo" size="md" width="1502" height="410" data-path="images/guides/developer/analyzer3.webp" />

Un rectángulo blanco corresponde a un nodo del pipeline, el rectángulo gris corresponde a los pasos del plan de consulta, y la `x` seguida de un número corresponde a la cantidad de entradas/salidas que se están utilizando. Si no desea verlos en formato compacto, siempre puede añadir `compact=0`:

```sql theme={null}
EXPLAIN PIPELINE graph = 1, compact = 0
WITH (
       SELECT count(*)
       FROM session_events
   ) AS total_rows
SELECT
   type,
   min(timestamp) AS minimum_date,
   max(timestamp) AS maximum_date,
   (count(*) / total_rows) * 100 AS percentage
FROM session_events
GROUP BY type
FORMAT TSV
```

```response theme={null}
digraph
{
 rankdir="LR";
 { node [shape = rect]
   n0[label="MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread)"];
   n1[label="ExpressionTransform"];
   n2[label="AggregatingTransform"];
   n3[label="Resize"];
   n4[label="ExpressionTransform"];
   n5[label="ExpressionTransform"];
 }
 n0 -> n1;
 n1 -> n2;
 n2 -> n3;
 n3 -> n4;
 n3 -> n5;
}
```

<Image img="https://mintcdn.com/private-7c7dfe99/k4wNHsd_gyvah7Fr/images/guides/developer/analyzer4.webp?fit=max&auto=format&n=k4wNHsd_gyvah7Fr&q=85&s=567cf828b84a596d89331b36edf7cd03" alt="Salida compacta del grafo" size="md" width="1412" height="246" data-path="images/guides/developer/analyzer4.webp" />

¿Por qué ClickHouse no lee de la tabla con varios hilos? Intentemos añadir más datos a nuestra tabla:

```sql theme={null}
INSERT INTO session_events SELECT * FROM generateRandom('clientId UUID,
   sessionId UUID,
   pageId UUID,
   timestamp DateTime,
   type Enum(\'type1\', \'type2\')', 1, 10, 2) LIMIT 1000000;
```

Ahora volvamos a ejecutar nuestra consulta `EXPLAIN`:

```sql theme={null}
EXPLAIN PIPELINE graph = 1, compact = 0
WITH (
       SELECT count(*)
       FROM session_events
   ) AS total_rows
SELECT
   type,
   min(timestamp) AS minimum_date,
   max(timestamp) AS maximum_date,
   (count(*) / total_rows) * 100 AS percentage
FROM session_events
GROUP BY type
FORMAT TSV
```

```response theme={null}
digraph
{
  rankdir="LR";
  { node [shape = rect]
    n0[label="MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread)"];
    n1[label="MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread)"];
    n2[label="ExpressionTransform"];
    n3[label="ExpressionTransform"];
    n4[label="StrictResize"];
    n5[label="AggregatingTransform"];
    n6[label="AggregatingTransform"];
    n7[label="Resize"];
    n8[label="ExpressionTransform"];
    n9[label="ExpressionTransform"];
  }
  n0 -> n2;
  n1 -> n3;
  n2 -> n4;
  n3 -> n4;
  n4 -> n5;
  n4 -> n6;
  n5 -> n7;
  n6 -> n7;
  n7 -> n8;
  n7 -> n9;
}
```

<Image img="https://mintcdn.com/private-7c7dfe99/k4wNHsd_gyvah7Fr/images/guides/developer/analyzer5.webp?fit=max&auto=format&n=k4wNHsd_gyvah7Fr&q=85&s=adc351828472f54f3f6b49bb41612e68" alt="Salida del grafo en paralelo" size="md" width="1492" height="240" data-path="images/guides/developer/analyzer5.webp" />

Así, el ejecutor decidió no paralelizar las operaciones porque el volumen de datos no era lo bastante alto. Al añadir más filas, el ejecutor pasó a usar varios hilos, como se muestra en el grafo.

<div id="executor">
  ## Ejecutor
</div>

Por último, el ejecutor se encarga del último paso de la ejecución de la consulta. Toma el pipeline de consulta y lo ejecuta. Hay distintos tipos de ejecutores, según se trate de un `SELECT`, un `INSERT` o un `INSERT SELECT`.
