deшка
Урок 10.6

Сортировка, первичный ключ и индексы

Как ключи выбирают диапазоны чтения: от сортировки строк до marks и конкретного фильтра.

В MergeTree сортировка объединяет близкие значения ключа, а разреженный индекс помогает исключить диапазоны, которые не подходят запросу. Первичный ключ здесь не проверяет уникальность строк. Его задача — уменьшить чтение.

ORDER BY и PRIMARY KEY

Пусть таблица событий содержит идентификатор, тип события и время:

CREATE TABLE events
(
    event_id UInt64,
    event_type LowCardinality(String),
    event_time DateTime('UTC')
)
ENGINE = MergeTree
PRIMARY KEY (event_type, event_time)
ORDER BY (event_type, event_time, event_id);

ORDER BY задаёт порядок строк внутри каждой части: тип события, затем время, затем ID. Для порядка вывода SELECT задают отдельный ORDER BY запроса.

PRIMARY KEY задаёт поля разреженного индекса и должен быть префиксом ORDER BY. По умолчанию он совпадает с сортировочным ключом. Более короткий первичный ключ уменьшает индекс, сохраняя дополнительную сортировку по ID.

Физическую группировку близких ключей называют кластеризацией. В других СУБД кластерный индекс связан с размещением данных, а некластерный хранит отдельную структуру ключей и ссылок. В MergeTree физический порядок задаёт ORDER BY, а первичный индекс содержит опорные значения для диапазонов строк.

Гранула, индекс и отметка

Гранула — группа строк, для которой индекс хранит опорное значение ключа. Поэтому индекс разреженный: в нём нет записи для каждой строки. Обычный ориентир index_granularity — 8192 строки, но адаптивная гранулярность учитывает и объём байтов; фактические группы могут быть меньше.

Представим для объяснения гранулы по две строки. После сортировки по типу и времени получились три группы:

ГранулаСтроки: тип / деньОпорный ключ
0click / 1 января; click / 2 январяclick / 1 января
1click / 3 января; view / 1 январяclick / 3 января
2view / 2 января; view / 3 январяview / 2 января

Для click за 2 января индекс позволяет исключить неподходящие группы. В подходящую гранулу входит и строка за 1 января: её отбросит WHERE после чтения. Индекс даёт диапазоны кандидатов, а не готовый точный результат.

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

Порядок полей в ключе

Ключ (event_type, event_time) удобен для фильтра по типу и времени. При отборе только по времени события могут находиться в диапазонах многих типов. Эффект фильтра по второму полю зависит от распределения первого поля и данных.

Первый столбец выбирают по частым фильтрам и распределению значений. Низкая кардинальность собирает похожие значения рядом и может улучшить сжатие. Уникальный ID в начале полезен для поиска по ID, но неудобен для отчёта по типу и времени.

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

Другие способы сократить чтение

Data-skipping index хранит сведения о блоках данных. Minmax знает диапазон значений, set — ограниченное множество, Bloom filter позволяет отсеять блок, когда искомого значения заведомо нет. Bloom filter может дать ложное совпадение, но тогда база просто прочитает лишний блок. Если почти каждый блок содержит все популярные значения, индекс мало поможет.

Projection — дополнительное физическое представление таблицы, например с другой сортировкой или предварительной агрегацией. Оно расходует место и ресурсы записи. Дополнительные индексы тоже требуют построения и проверки: структура, которая почти ничего не отсекает, может замедлить SELECT. Их применение проверяют по EXPLAIN и фактическому чтению.

Войти, чтобы отмечатьдальше →
10.7 →Партиции и жизненный цикл данных