> ## 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.

# Как понять выполнение запросов с помощью анализатора

> Описывает, как с помощью анализатора понять, как ClickHouse выполняет запросы

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 обрабатывает запросы чрезвычайно быстро, но выполнение запроса — не такой уж простой процесс. Давайте попробуем понять, как выполняется запрос `SELECT`. Для наглядности добавим в таблицу 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;
```

Теперь, когда в ClickHouse есть данные, можно выполнить несколько запросов и понять, как они исполняются. Выполнение запроса разбивается на множество этапов. Каждый этап выполнения запроса можно проанализировать и диагностировать с помощью соответствующего запроса `EXPLAIN`. Эти этапы показаны на схеме ниже:

<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="Этапы выполнения запроса Explain" size="md" width="2048" height="960" data-path="images/guides/developer/analyzer1.webp" />

Давайте посмотрим, как каждый компонент работает в процессе выполнения запроса. Мы возьмем несколько запросов, а затем разберем их с помощью оператора `EXPLAIN`.

<div id="parser">
  ## Парсер
</div>

Задача парсера — преобразовать текст запроса в AST (абстрактное синтаксическое дерево). Этот шаг можно наглядно представить с помощью `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               │
└────────────────────────────────────────────────────┘
```

Результатом является абстрактное синтаксическое дерево, которое можно визуализировать, как показано ниже:

<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="Вывод AST" size="md" width="2048" height="1058" data-path="images/guides/developer/analyzer2.webp" />

У каждого узла есть соответствующие дочерние элементы, а всё дерево в целом отражает общую структуру вашего запроса. Это логическая структура, помогающая обрабатывать запрос. С точки зрения конечного пользователя (если только его не интересует выполнение запроса) она не слишком полезна; этот инструмент в основном используется разработчиками.

<div id="analyzer">
  ## Анализатор
</div>

В настоящее время в ClickHouse есть две архитектуры анализатора. Чтобы использовать старую архитектуру, задайте: `enable_analyzer=0`. Текущая архитектура включена по умолчанию, начиная с ClickHouse `24.3`. Здесь мы будем описывать только текущую архитектуру, поскольку старая объявлена устаревшей и сохраняется только для обратной совместимости.

<Note>
  Текущая архитектура должна обеспечить более качественную основу для повышения производительности ClickHouse. Однако, поскольку это фундаментальный компонент процесса обработки запросов, она также может негативно повлиять на некоторые запросы, и у нее есть [известные несовместимости](/docs/ru/guides/clickhouse/performance-and-monitoring/analyzer#known-incompatibilities). Вы можете вернуться к старой архитектуре, изменив настройку `enable_analyzer` на уровне запроса или пользователя.
</Note>

Анализатор — важный этап выполнения запроса. Он принимает AST и преобразует его в дерево запроса. Основное преимущество дерева запроса перед AST в том, что многие компоненты в нем уже будут разрешены, например хранилище. Мы также уже знаем, из какой таблицы читать, разрешены псевдонимы, а дереву известны различные используемые типы данных. Благодаря этому анализатор может применять оптимизации. Эти оптимизации реализованы в виде "проходов". Каждый проход ищет свои варианты оптимизации. Вы можете увидеть все проходы [здесь](https://github.com/ClickHouse/ClickHouse/blob/76578ebf92af3be917cd2e0e17fea2965716d958/src/Analyzer/QueryTreePassManager.cpp#L249), а теперь давайте посмотрим, как это работает на практике, на нашем предыдущем запросе:

```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                                                  │
└───────────────────────────────────────────────────────────────────────────────────────────┘
```

Между этими двумя выполнениями видно, как разрешаются псевдонимы и проекции.

<div id="planner">
  ## Планировщик
</div>

Планировщик берет дерево запроса и на его основе строит план запроса. Дерево запроса показывает, что именно мы хотим сделать с конкретным запросом, а план запроса — как мы будем это делать. В рамках плана запроса также выполняются дополнительные оптимизации. Чтобы посмотреть план запроса, можно использовать `EXPLAIN PLAN` или `EXPLAIN` (`EXPLAIN` выполнит `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) │
└──────────────────────────────────────────────────┘
```

Хотя это и дает нам некоторую информацию, можно узнать и больше. Например, нам может понадобиться узнать имя столбца, для которого нужны проекции. Вы можете добавить заголовок в запрос:

```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                        │
└──────────────────────────────────────────────────┘
```

Итак, теперь вы знаете имена столбцов, которые нужно создать для последней Projection (`minimum_date`, `maximum_date` и `percentage`), но вам также может понадобиться более подробная информация обо всех действиях, которые необходимо выполнить. Для этого задайте `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                                                                                                                          │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

Теперь вы можете видеть все используемые входные данные, функции, псевдонимы и типы данных. С некоторыми оптимизациями, которые планировщик будет применять, можно ознакомиться [здесь](https://github.com/ClickHouse/ClickHouse/blob/master/src/Processors/QueryPlan/Optimizations/Optimizations.h).

<div id="query-pipeline">
  ## Конвейер запроса
</div>

Конвейер запроса формируется на основе плана запроса. Он очень похож на план запроса, однако, в отличие от него, представляет собой не дерево, а граф. Конвейер наглядно показывает, как ClickHouse будет выполнять запрос и какие ресурсы при этом задействует. Анализ конвейера запроса особенно полезен для выявления узких мест с точки зрения операций ввода/вывода. Рассмотрим предыдущий запрос и изучим выполнение его конвейера:

```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 │
└────────────────────────────────────────────────────────────────────────────┘
```

В скобках указан шаг плана запроса, а рядом с ним — процессор. Это весьма полезная информация, однако, поскольку перед нами граф, было бы удобно визуализировать его соответствующим образом. Для этого существует настройка `graph`, которой можно присвоить значение 1, указав формат вывода 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=""];
}
```

Затем скопируйте этот вывод и вставьте его [сюда](https://dreampuf.github.io/GraphvizOnline) — будет сгенерирован следующий граф:

<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="Графический вывод" size="md" width="1502" height="410" data-path="images/guides/developer/analyzer3.webp" />

Белый прямоугольник соответствует узлу конвейера, серый прямоугольник — шагам плана запроса, а символ `x`, за которым следует число, обозначает количество используемых входов/выходов. Если вы не хотите видеть их в компактном виде, всегда можно добавить `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="Вывод компактного графа" size="md" width="1412" height="246" data-path="images/guides/developer/analyzer4.webp" />

Почему ClickHouse не читает данные из таблицы в несколько потоков? Попробуем добавить в таблицу больше данных:

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

Теперь давайте снова выполним наш запрос `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="Вывод параллельного графа" size="md" width="1492" height="240" data-path="images/guides/developer/analyzer5.webp" />

Итак, исполнитель решил не выполнять операции параллельно, потому что объём данных был недостаточно большим. После добавления большего количества строк исполнитель решил использовать несколько потоков, как показано на графе.

<div id="executor">
  ## Исполнитель
</div>

Наконец, завершающий этап выполнения запроса осуществляется исполнителем. Он берёт конвейер выполнения запроса и запускает его. Существуют разные типы исполнителей — в зависимости от того, выполняете ли вы `SELECT`, `INSERT` или `INSERT SELECT`.
