Перейти к основному содержимому
Перейти к основному содержимому

Первичные индексы

Ищете подробности о продвинутых индексах?

Эта страница представляет собой описание разреженного первичного индекса 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 записей из первичного индекса одной из текущих частей данных ^^parts^^. Обратите внимание, что эти ^^части^^ непрерывно сливаются в фоновом режиме в более крупные ^^части^^:

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, чтобы увидеть, как первичные индексы всех частей данных ^^parts^^ используются для пропуска гранул, которые не могут содержать строки, соответствующие предикатам примерного запроса. Эти гранулы исключаются из загрузки и обработки:

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 million
   └────────────┘

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 million
   └──────────┘

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

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

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

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

  • Во время запросов индекс позволяет ClickHouse пропускать гранулы, уменьшая ввод-вывод и использование памяти, одновременно ускоряя производительность.

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

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

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

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