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

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

Ищете подробную информацию о расширенной индексации?

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

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

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


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

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

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

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



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

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



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

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

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



① Пример запроса включает предикат на оба столбца ^^primary key^^: 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 гранул во всех ^^parts^^ были выбраны для обработки при анализе первичного индекса. Остальные гранулы были полностью пропущены.

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

SELECT max(price)
FROM uk.uk_price_paid_simple
WHERE (town = 'LONDON') AND (street = 'OXFORD STREET');
   ┌─max(price)─┐
1. │  263100000 │ -- 263,10 миллиона
   └────────────┘

1 строка в наборе. Затрачено: 0,010 сек. Обработано 24,58 тыс. строк, 159,04 КБ (2,53 млн строк/сек., 16,35 МБ/сек.)
Пиковое использование памяти: 13,00 МиБ.

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

SELECT count() FROM uk.uk_price_paid_simple;
   ┌──count()─┐
1. │ 29556244 │ -- 29,56 миллионов
   └──────────┘

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

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

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

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

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

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

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

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

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