> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-home-button.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> PREWHERE снижает I/O, избегая чтения ненужных данных из столбцов.

# Как работает оптимизация PREWHERE?

export const Image = ({img, alt, size}) => {
  return <Frame>
      <img src={img} alt={alt} />
    </Frame>;
};

[Предложение PREWHERE](/ru/reference/statements/select/prewhere) — это оптимизация выполнения запросов в ClickHouse. Она уменьшает I/O и ускоряет запросы, избегая лишнего чтения данных и отфильтровывая нерелевантные данные до чтения с диска столбцов, не используемых для фильтрации.

В этом руководстве объясняется, как работает PREWHERE, как измерить его влияние и как настроить его для достижения максимальной производительности.

<div id="query-processing-without-prewhere-optimization">
  ## Обработка запроса без оптимизации PREWHERE
</div>

Для начала рассмотрим, как обрабатывается запрос к таблице [uk\_price\_paid\_simple](/ru/concepts/core-concepts/parts) без использования PREWHERE:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/k6dUBbEUOmZKxz3v/images/guides/best-practices/prewhere_01.gif?s=2ddf151e058208eb44d0119ca24488ba" size="md" alt="Обработка запроса без оптимизации PREWHERE" width="1181" height="1004" data-path="images/guides/best-practices/prewhere_01.gif" />

<br />

<br />

① Запрос содержит фильтр по столбцу `town`, который входит в первичный ключ таблицы, а значит — и в первичный индекс.

② Чтобы ускорить выполнение запроса, ClickHouse загружает первичный индекс таблицы в память.

③ Затем ClickHouse просматривает записи индекса, чтобы определить, какие гранулы столбца `town` могут содержать строки, соответствующие условию.

④ Эти потенциально подходящие гранулы загружаются в память вместе с позиционно выровненными гранулами из других столбцов, необходимых для запроса.

⑤ После этого оставшиеся фильтры применяются во время выполнения запроса.

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

<div id="how-prewhere-improves-query-efficiency">
  ## Как PREWHERE повышает эффективность запроса
</div>

Следующие анимации показывают, как обрабатывается приведённый выше запрос, когда предложение PREWHERE применяется ко всем условиям запроса.

Первые три шага обработки такие же, как и раньше:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/prewhere_02.gif?s=ce3ce7464a6cdbe754a9de7f1785d316" size="md" alt="Обработка запроса с оптимизацией PREWHERE" width="1190" height="1004" data-path="images/guides/best-practices/prewhere_02.gif" />

<br />

<br />

① Запрос включает фильтр по столбцу `town`, который входит в первичный ключ таблицы, а значит — и в первичный индекс.

②  Как и при выполнении без предложения PREWHERE, для ускорения запроса ClickHouse загружает первичный индекс в память,

③ а затем просматривает записи индекса, чтобы определить, какие гранулы столбца `town` могут содержать строки, соответствующие условию.

Теперь, благодаря предложению PREWHERE, следующий шаг выглядит иначе: вместо того чтобы сразу читать все нужные столбцы, ClickHouse фильтрует данные по одному столбцу за раз, загружая только то, что действительно необходимо. Это значительно сокращает объём I/O, особенно для широких таблиц.

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

**Шаг 1: Фильтрация по town**<br />
ClickHouse начинает обработку PREWHERE с ① чтения выбранных гранул столбца `town` и проверки, какие из них действительно содержат строки, соответствующие `London`.

В нашем примере все выбранные гранулы подходят, поэтому ② для обработки выбираются соответствующие им позиционно выровненные гранулы следующего столбца фильтра — `date`:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/prewhere_03.gif?s=c4783feddadda32a700907a9796a310a" size="md" alt="Шаг 1: Фильтрация по town" width="1209" height="1003" data-path="images/guides/best-practices/prewhere_03.gif" />

<br />

<br />

**Шаг 2: Фильтрация по date**<br />
Далее ClickHouse ① читает выбранные гранулы столбца `date`, чтобы проверить условие `date > '2024-12-31'`.

В этом случае две из трёх гранул содержат подходящие строки, поэтому ② для дальнейшей обработки выбираются только их позиционно выровненные гранулы из следующего столбца фильтра — `price`:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/prewhere_04.gif?s=9dfd5ef835644f2f5c617a8ce78e9d24" size="md" alt="Шаг 2: Фильтрация по date" width="1181" height="1004" data-path="images/guides/best-practices/prewhere_04.gif" />

<br />

<br />

**Шаг 3: Фильтрация по price**<br />
Наконец, ClickHouse ① читает две выбранные гранулы столбца `price`, чтобы проверить последнее условие `price > 10_000`.

Только одна из двух гранул содержит подходящие строки, поэтому ② для дальнейшей обработки нужно загрузить только её позиционно выровненную гранулу из столбца `SELECT` — `street`:

<Image img="https://mintcdn.com/private-7c7dfe99-home-button/kYq9dTBOYcMERk1I/images/guides/best-practices/prewhere_05.gif?s=ac4f07764ba139dc847ae95e9f630939" size="md" alt="Шаг 2: Фильтрация по price" width="1209" height="1003" data-path="images/guides/best-practices/prewhere_05.gif" />

<br />

<br />

На последнем шаге загружается только минимальный набор гранул столбцов — тех, что содержат подходящие строки. Это снижает использование памяти, уменьшает дисковый I/O и ускоряет выполнение запроса.

<Info>
  **PREWHERE уменьшает объём читаемых данных, а не количество обрабатываемых строк**

  Обратите внимание: ClickHouse обрабатывает одинаковое количество строк как в версии запроса с PREWHERE, так и без него. Однако при использовании оптимизации PREWHERE не все значения столбцов нужно загружать для каждой обрабатываемой строки.
</Info>

<div id="prewhere-optimization-is-automatically-applied">
  ## Оптимизация PREWHERE применяется автоматически
</div>

Предложение PREWHERE можно добавить вручную, как показано в примере выше. Однако писать PREWHERE вручную не требуется. Когда включена настройка [`optimize_move_to_prewhere`](/ru/reference/settings/session-settings#optimize_move_to_prewhere) (по умолчанию `true`), ClickHouse автоматически переносит условия фильтрации из WHERE в PREWHERE, отдавая приоритет тем, которые сильнее всего сокращают объем чтения.

Идея состоит в том, что столбцы меньшего размера сканируются быстрее, и к моменту обработки более крупных столбцов большинство гранул уже отфильтровано. Поскольку во всех столбцах одинаковое количество строк, размер столбца в первую очередь определяется его типом данных: например, столбец `UInt8` обычно значительно меньше, чем столбец `String`.

Начиная с версии [23.2](https://clickhouse.com/blog/clickhouse-release-23-02#multi-stage-prewhere--alexander-gololobov), ClickHouse по умолчанию использует эту стратегию, сортируя столбцы фильтра PREWHERE для многоэтапной обработки по возрастанию несжатого размера.

Начиная с версии [23.11](https://clickhouse.com/blog/clickhouse-release-23-11#column-statistics-for-prewhere), необязательная статистика столбцов может дополнительно повысить эффективность, выбирая порядок обработки фильтров на основе фактической селективности данных, а не только размера столбцов.

<div id="how-to-measure-prewhere-impact">
  ## Как измерить влияние PREWHERE
</div>

Чтобы убедиться, что PREWHERE действительно улучшает выполнение запросов, можно сравнить их производительность при включенном и отключенном параметре `optimize_move_to_prewhere`.

Начнем с выполнения запроса при отключенном параметре `optimize_move_to_prewhere`:

```sql theme={null}
SELECT
    street
FROM
   uk.uk_price_paid_simple
WHERE
   town = 'LONDON' AND date > '2024-12-31' AND price < 10_000
SETTINGS optimize_move_to_prewhere = false;
```

```txt theme={null}
   ┌─street──────┐
1. │ MOYSER ROAD │
2. │ AVENUE ROAD │
3. │ AVENUE ROAD │
   └─────────────┘

3 rows in set. Elapsed: 0.056 sec. Processed 2.31 million rows, 23.36 MB (41.09 million rows/s., 415.43 MB/s.)
Peak memory usage: 132.10 MiB.
```

ClickHouse прочитал **23.36 MB** данных из столбцов при обработке 2.31 миллиона строк во время выполнения запроса.

Далее выполним запрос с включённым параметром `optimize_move_to_prewhere`. (Обратите внимание: этот параметр необязателен, так как он включён по умолчанию):

```sql theme={null}
SELECT
    street
FROM
   uk.uk_price_paid_simple
WHERE
   town = 'LONDON' AND date > '2024-12-31' AND price < 10_000
SETTINGS optimize_move_to_prewhere = true;
```

```txt theme={null}
   ┌─street──────┐
1. │ MOYSER ROAD │
2. │ AVENUE ROAD │
3. │ AVENUE ROAD │
   └─────────────┘

3 rows in set. Elapsed: 0.017 sec. Processed 2.31 million rows, 6.74 MB (135.29 million rows/s., 394.44 MB/s.)
Peak memory usage: 132.11 MiB.
```

Было обработано то же количество строк (2,31 миллиона), но благодаря PREWHERE ClickHouse прочитал более чем в три раза меньше данных из столбцов — всего 6,74 МБ вместо 23,36 МБ, — что сократило общее время выполнения в 3 раза.

Чтобы лучше понять, как ClickHouse применяет PREWHERE «за кулисами», используйте EXPLAIN и трассировочные логи.

Рассмотрим логический план запроса с помощью конструкции [EXPLAIN](/ru/reference/statements/explain#explain-plan):

```sql theme={null}
EXPLAIN PLAN actions = 1
SELECT
    street
FROM
   uk.uk_price_paid_simple
WHERE
   town = 'LONDON' and date > '2024-12-31' and price < 10_000;
```

```txt theme={null}
...
Prewhere info                                                                                                                                                                                                                                          
  Prewhere filter column: 
    and(greater(__table1.date, '2024-12-31'_String), 
    less(__table1.price, 10000_UInt16), 
    equals(__table1.town, 'LONDON'_String)) 
...
```

Здесь мы опускаем большую часть вывода плана, так как он довольно объёмный. По сути, он показывает, что все три предиката по столбцам были автоматически перенесены в PREWHERE.

Если вы воспроизведёте это самостоятельно, то также увидите в плане запроса, что порядок этих предикатов определяется размером типов данных столбцов. Поскольку мы не включили статистику по столбцам, ClickHouse использует размер как резервный критерий для определения порядка обработки в PREWHERE.

Если вы хотите заглянуть ещё глубже во внутренние механизмы, можно проследить каждый отдельный шаг обработки PREWHERE, указав ClickHouse возвращать все записи журнала уровня test во время выполнения запроса:

```sql theme={null}
SELECT
    street
FROM
   uk.uk_price_paid_simple
WHERE
   town = 'LONDON' AND date > '2024-12-31' AND price < 10_000
SETTINGS send_logs_level = 'test';
```

```txt theme={null}
...
<Trace> ... Condition greater(date, '2024-12-31'_String) moved to PREWHERE
<Trace> ... Condition less(price, 10000_UInt16) moved to PREWHERE
<Trace> ... Condition equals(town, 'LONDON'_String) moved to PREWHERE
...
<Test> ... Executing prewhere actions on block: greater(__table1.date, '2024-12-31'_String)
<Test> ... Executing prewhere actions on block: less(__table1.price, 10000_UInt16)
...
```

<div id="key-takeaways">
  ## Ключевые выводы
</div>

* PREWHERE позволяет не читать данные столбцов, которые впоследствии будут отфильтрованы, экономя I/O и память.
* Это происходит автоматически, если включен `optimize_move_to_prewhere` (по умолчанию).
* Порядок фильтрации важен: небольшие и селективные столбцы должны идти первыми.
* Используйте `EXPLAIN` и журналы, чтобы проверить, применяется ли PREWHERE, и понять его влияние.
* PREWHERE дает наибольший эффект на широких таблицах и при сканировании больших объемов данных с селективными фильтрами.
