> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-detect-table-modification.mintlify.site/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>;
};

<Tip>
  **Ищете подробную информацию о расширенных возможностях индексирования?**

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

  Подробнее о продвинутых стратегиях индексирования и технических нюансах см. в руководстве [Подробно о первичных индексах](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes).
</Tip>

<div id="how-does-the-sparse-primary-index-work-in-clickHouse">
  ## Как работает разреженный первичный индекс в ClickHouse?
</div>

<br />

Разреженный первичный индекс в ClickHouse помогает эффективно определять [гранулы](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes#data-is-organized-into-granules-for-parallel-data-processing) — блоки строк, — которые могут содержать данные, соответствующие условию запроса по столбцам первичного ключа таблицы. В следующем разделе мы объясним, как этот индекс строится на основе значений этих столбцов.

<div id="sparse-primary-index-creation">
  ### Создание разреженного первичного индекса
</div>

Чтобы проиллюстрировать, как строится разреженный первичный индекс, мы используем таблицу [uk\_price\_paid\_simple](/ru/concepts/core-concepts/parts) и несколько анимаций.

Как [мы уже отмечали](/ru/concepts/core-concepts/parts), в нашей ① примерной таблице с первичным ключом (town, street) ② вставленные данные ③ сохраняются на диске, сортируются по значениям столбцов первичного ключа и сжимаются в отдельные файлы для каждого столбца:

<Image img="https://mintcdn.com/private-7c7dfe99-detect-table-modification/xkZ8XPhBsPAc7Vbw/images/managing-data/core-concepts/primary-index-light_01.webp?fit=max&auto=format&n=xkZ8XPhBsPAc7Vbw&q=85&s=accc203dc14744df95465283ac44a306" size="lg" width="942" height="1004" data-path="images/managing-data/core-concepts/primary-index-light_01.webp" />

<br />

<br />

Для обработки данные каждого столбца ④ логически делятся на гранулы — каждая охватывает 8 192 строки, — которые являются наименьшими единицами, с которыми работают механизмы обработки данных ClickHouse.

Именно такая структура гранул делает первичный индекс **разреженным**: вместо индексации каждой строки ClickHouse сохраняет ⑤ значения первичного ключа только из одной строки на гранулу — а именно из первой. В результате получается одна запись индекса на гранулу:

<Image img="https://mintcdn.com/private-7c7dfe99-detect-table-modification/xkZ8XPhBsPAc7Vbw/images/managing-data/core-concepts/primary-index-light_02.webp?fit=max&auto=format&n=xkZ8XPhBsPAc7Vbw&q=85&s=da51f648395b77e873a56b0500479a8c" size="lg" width="1424" height="1004" data-path="images/managing-data/core-concepts/primary-index-light_02.webp" />

<br />

<br />

Благодаря своей разреженности первичный индекс достаточно мал, чтобы полностью помещаться в памяти, что обеспечивает быструю фильтрацию запросов с предикатами по столбцам первичного ключа. В следующем разделе мы покажем, как он помогает ускорять такие запросы.

<div id="primary-index-usage">
  ### Использование первичного индекса
</div>

Ниже схематично показано, как разреженный первичный индекс используется для ускорения запросов:

<Image img="https://mintcdn.com/private-7c7dfe99-detect-table-modification/xkZ8XPhBsPAc7Vbw/images/managing-data/core-concepts/primary-index-light_03.webp?fit=max&auto=format&n=xkZ8XPhBsPAc7Vbw&q=85&s=7f12ada48fe4fc75a017ba7d02b625ef" size="lg" width="1087" height="948" data-path="images/managing-data/core-concepts/primary-index-light_03.webp" />

<br />

<br />

① Пример запроса содержит условие по обоим столбцам первичного ключа: `town = 'LONDON' AND street = 'OXFORD STREET'`.

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

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

④ Затем эти потенциально релевантные гранулы загружаются и [обрабатываются](/ru/concepts/core-concepts/query-parallelism) в памяти вместе с соответствующими гранулами из всех остальных столбцов, необходимых для выполнения запроса.

<div id="monitoring-primary-indexes">
  ## Мониторинг первичных индексов
</div>

Каждая [часть данных](/ru/concepts/core-concepts/parts) в таблице имеет собственный первичный индекс. Мы можем просмотреть содержимое этих индексов с помощью табличной функции [mergeTreeIndex](/ru/reference/functions/table-functions/mergeTreeIndex).

Следующий запрос выводит количество записей в первичном индексе для каждой части данных таблицы из нашего примера:

```sql theme={null}
SELECT
    part_name,
    max(mark_number) AS entries
FROM mergeTreeIndex('uk', 'uk_price_paid_simple')
GROUP BY part_name;
```

```txt theme={null}
   ┌─part_name─┬─entries─┐
1. │ all_2_2_0 │     914 │
2. │ all_1_1_0 │    1343 │
3. │ all_0_0_0 │    1349 │
   └───────────┴─────────┘
```

Этот запрос показывает первые 10 записей из первичного индекса одной из текущих частей данных. Обратите внимание, что эти части постоянно [сливаются](/ru/concepts/core-concepts/merges) в фоновом режиме в более крупные части:

```sql theme={null}
SELECT 
    mark_number + 1 AS entry,
    town,
    street
FROM mergeTreeIndex('uk', 'uk_price_paid_simple')
WHERE part_name = (SELECT any(part_name) FROM mergeTreeIndex('uk', 'uk_price_paid_simple')) 
ORDER BY mark_number ASC
LIMIT 10;
```

```txt theme={null}
    ┌─entry─┬─town───────────┬─street───────────┐
 1. │     1 │ ABBOTS LANGLEY │ ABBEY DRIVE      │
 2. │     2 │ ABERDARE       │ RICHARDS TERRACE │
 3. │     3 │ ABERGELE       │ PEN Y CAE        │
 4. │     4 │ ABINGDON       │ CHAMBRAI CLOSE   │
 5. │     5 │ ABINGDON       │ THORNLEY CLOSE   │
 6. │     6 │ ACCRINGTON     │ MAY HILL CLOSE   │
 7. │     7 │ ADDLESTONE     │ HARE HILL        │
 8. │     8 │ ALDEBURGH      │ LINDEN ROAD      │
 9. │     9 │ ALDERSHOT      │ HIGH STREET      │
10. │    10 │ ALFRETON       │ ALMA STREET      │
    └───────┴────────────────┴──────────────────┘
```

Наконец, мы используем предложение [EXPLAIN](/ru/reference/statements/explain), чтобы увидеть, как первичные индексы всех частей данных используются для пропуска гранул, которые заведомо не могут содержать строки, соответствующие предикатам запроса из примера. Эти гранулы исключаются из загрузки и обработки:

```sql theme={null}
EXPLAIN indexes = 1
SELECT
    max(price)
FROM
    uk.uk_price_paid_simple
WHERE
    town = 'LONDON' AND street = 'OXFORD STREET';
```

```txt theme={null}
    ┌─explain────────────────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))                                                                  │
 2. │   Aggregating                                                                                              │
 3. │     Expression (Before GROUP BY)                                                                           │
 4. │       Expression                                                                                           │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple)                                                        │
 6. │         Indexes:                                                                                           │
 7. │           PrimaryKey                                                                                       │
 8. │             Keys:                                                                                          │
 9. │               town                                                                                         │
10. │               street                                                                                       │
11. │             Condition: and((street in ['OXFORD STREET', 'OXFORD STREET']), (town in ['LONDON', 'LONDON'])) │
12. │             Parts: 3/3                                                                                     │
13. │             Granules: 3/3609                                                                               │
    └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

Обратите внимание, что строка 13 в приведённом выше выводе EXPLAIN показывает, что анализ первичного индекса отобрал для обработки только 3 из 3 609 гранул среди всех частей данных. Остальные гранулы были полностью пропущены.

Это также видно, если просто выполнить запрос:

```sql theme={null}
SELECT max(price)
FROM uk.uk_price_paid_simple
WHERE (town = 'LONDON') AND (street = 'OXFORD STREET');
```

```txt theme={null}
   ┌─max(price)─┐
1. │  263100000 │ -- 263,10 миллиона
   └────────────┘

1 row in set. Elapsed: 0.010 sec. Processed 24.58 thousand rows, 159.04 KB (2.53 million rows/s., 16.35 MB/s.)
Peak memory usage: 13.00 MiB.
```

Как показано выше, в таблице из примера было обработано лишь около 25 000 строк из примерно 30 миллионов строк:

```sql theme={null}
SELECT count() FROM uk.uk_price_paid_simple;
```

```txt theme={null}
   ┌──count()─┐
1. │ 29556244 │ -- 29,56 миллиона
   └──────────┘
```

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

* **Разреженные первичные индексы** помогают ClickHouse пропускать лишние данные, определяя, какие гранулы могут содержать строки, соответствующие условиям запроса по столбцам первичного ключа.

* Каждый индекс хранит только значения первичного ключа из **первой строки каждой гранулы** (по умолчанию гранула содержит 8 192 строки), поэтому он достаточно компактен, чтобы умещаться в памяти.

* **Каждая часть данных** в таблице MergeTree имеет **собственный первичный индекс**, который используется независимо при выполнении запроса.

* Во время выполнения запросов индекс позволяет ClickHouse **пропускать гранулы**, сокращая I/O и использование памяти, что повышает производительность.

* Вы можете **просматривать содержимое индекса** с помощью табличной функции `mergeTreeIndex` и отслеживать использование индекса с помощью предложения `EXPLAIN`.

<div id="where-to-find-more-information">
  ## Где найти дополнительную информацию
</div>

Если вы хотите подробнее разобраться в том, как в ClickHouse работают разреженные первичные индексы, чем они отличаются от традиционных индексов баз данных и какие есть рекомендации по их использованию, ознакомьтесь с нашим подробным материалом об индексировании [здесь](/ru/guides/clickhouse/data-modelling/sparse-primary-indexes).

Если вас интересует, как ClickHouse с высокой степенью параллелизма обрабатывает данные, выбранные при сканировании первичного индекса, см. руководство по параллелизму запросов [здесь](/ru/concepts/core-concepts/query-parallelism).
