Перейти к основному содержанию
Ищете подробную информацию о расширенных возможностях индексирования?На этой странице рассказывается о разреженном первичном индексе ClickHouse: как он строится, как работает и как помогает ускорять выполнение запросов.Подробнее о продвинутых стратегиях индексирования и технических нюансах см. в руководстве Подробно о первичных индексах.

Как работает разреженный первичный индекс в ClickHouse?


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

Создание разреженного первичного индекса

Чтобы проиллюстрировать, как строится разреженный первичный индекс, мы используем таблицу uk_price_paid_simple и несколько анимаций. Как мы уже отмечали, в нашей ① примерной таблице с первичным ключом (town, street) ② вставленные данные ③ сохраняются на диске, сортируются по значениям столбцов первичного ключа и сжимаются в отдельные файлы для каждого столбца:

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

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

Использование первичного индекса

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

① Пример запроса содержит условие по обоим столбцам первичного ключа: town = 'LONDON' AND street = 'OXFORD STREET'. ② Чтобы ускорить запрос, ClickHouse загружает первичный индекс таблицы в память. ③ Затем он просматривает записи индекса, чтобы определить, какие гранулы могут содержать строки, соответствующие условию, — иными словами, какие гранулы нельзя пропустить. ④ Затем эти потенциально релевантные гранулы загружаются и обрабатываются в памяти вместе с соответствующими гранулами из всех остальных столбцов, необходимых для выполнения запроса.

Мониторинг первичных индексов

Каждая часть данных в таблице имеет собственный первичный индекс. Мы можем просмотреть содержимое этих индексов с помощью табличной функции mergeTreeIndex. Следующий запрос выводит количество записей в первичном индексе для каждой части данных таблицы из нашего примера:
SELECT
    part_name,
    max(mark_number) AS entries
FROM mergeTreeIndex('uk', 'uk_price_paid_simple')
GROUP BY part_name;
   ┌─part_name─┬─entries─┐
1. │ all_2_2_0 │     914 │
2. │ all_1_1_0 │    1343 │
3. │ all_0_0_0 │    1349 │
   └───────────┴─────────┘
Этот запрос показывает первые 10 записей из первичного индекса одной из текущих частей данных. Обратите внимание, что эти части постоянно сливаются в фоновом режиме в более крупные части:
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;
    ┌─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, чтобы увидеть, как первичные индексы всех частей данных используются для пропуска гранул, которые заведомо не могут содержать строки, соответствующие предикатам запроса из примера. Эти гранулы исключаются из загрузки и обработки:
EXPLAIN indexes = 1
SELECT
    max(price)
FROM
    uk.uk_price_paid_simple
WHERE
    town = 'LONDON' AND street = 'OXFORD STREET';
    ┌─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 гранул среди всех частей данных. Остальные гранулы были полностью пропущены. Это также видно, если просто выполнить запрос:
SELECT max(price)
FROM uk.uk_price_paid_simple
WHERE (town = 'LONDON') AND (street = 'OXFORD STREET');
   ┌─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 миллионов строк:
SELECT count() FROM uk.uk_price_paid_simple;
   ┌──count()─┐
1. │ 29556244 │ -- 29,56 миллиона
   └──────────┘

Ключевые выводы

  • Разреженные первичные индексы помогают ClickHouse пропускать лишние данные, определяя, какие гранулы могут содержать строки, соответствующие условиям запроса по столбцам первичного ключа.
  • Каждый индекс хранит только значения первичного ключа из первой строки каждой гранулы (по умолчанию гранула содержит 8 192 строки), поэтому он достаточно компактен, чтобы умещаться в памяти.
  • Каждая часть данных в таблице MergeTree имеет собственный первичный индекс, который используется независимо при выполнении запроса.
  • Во время выполнения запросов индекс позволяет ClickHouse пропускать гранулы, сокращая I/O и использование памяти, что повышает производительность.
  • Вы можете просматривать содержимое индекса с помощью табличной функции mergeTreeIndex и отслеживать использование индекса с помощью предложения EXPLAIN.

Где найти дополнительную информацию

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