mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-14 11:33:46 +00:00
189 lines
18 KiB
Markdown
189 lines
18 KiB
Markdown
<a name="table_engines-mergetree"></a>
|
||
|
||
# MergeTree
|
||
|
||
Движок `MergeTree`, а также другие движки этого семейства (`*MergeTree`) — это наиболее функциональные движки таблиц ClickHousе.
|
||
|
||
!!!info
|
||
Движок [Merge](merge.md#table_engine-merge) не относится к семейству `*MergeTree`.
|
||
|
||
Основные возможности:
|
||
|
||
- Хранит данные, отсортированные по первичному ключу.
|
||
|
||
Это позволяет создавать разреженный индекс небольшого объёма, который позволяет быстрее находить данные.
|
||
|
||
- Позволяет оперировать партициями, если задан [ключ партиционирования](custom_partitioning_key.md#table_engines-custom_partitioning_key).
|
||
|
||
ClickHouse поддерживает отдельные операции с партициями, которые работают эффективнее, чем общие операции с этим же результатом над этими же данными. Также, ClickHouse автоматически отсекает данные по партициям там, где ключ партиционирования указан в запросе. Это также увеличивает эффективность выполнения запросов.
|
||
|
||
- Поддерживает репликацию данных.
|
||
|
||
Для этого требуется [преобразование](replication.md#convert-mergetree-to-replicated) `MergeTree` к `ReplicatedMergeTree`. Подробнее читайте в разделе [Репликация данных](replication.md#table_engines-replication).
|
||
|
||
- Поддерживает сэмплирование данных.
|
||
|
||
При необходимости можно задать способ сэмплирования данных в таблице.
|
||
|
||
|
||
!!!attention
|
||
При создании таблицы помните, что она обязательно должна содержать столбец с датой типа [Date](../../data_types/date.md#data_type-date).
|
||
|
||
## Конфигурирование движка при создании таблицы
|
||
|
||
```
|
||
ENGINE [=] MergeTree() [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
|
||
```
|
||
|
||
**Секции ENGINE**
|
||
|
||
- `PARTITION BY` — [ключ партиционирования](custom_partitioning_key.md#table_engines-custom_partitioning_key).
|
||
|
||
Для партиционирования по месяцам используйте выражение `toYYYYMM(date_column)`, где `date_column` — столбец с датой типа [Date](../../data_types/date.md#data_type-date). В этом случае имена партиций имеют формат `"YYYYMM"`.
|
||
|
||
- `ORDER BY` — первичный ключ.
|
||
|
||
Тип — [Tuple()](../../data_types/tuple.md#data_type-tuple). Может состоять из произвольных выражений, но обычно это кортеж столбцов. Обязательно должен включать в себя выражение для сэмплирования, если оно задано.
|
||
|
||
- `SAMPLE BY` — выражение для сэмплирования.
|
||
- `SETTINGS` — дополнительные параметры, регулирующие поведение `MergeTree`:
|
||
|
||
- `index_granularity` — гранулярность индекса. Число строк данных между «засечками» индекса. По умолчанию — 8192.
|
||
|
||
**Пример**
|
||
|
||
```
|
||
ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity=8192
|
||
```
|
||
|
||
В примере мы устанавливаем партиционирование по месяцам.
|
||
|
||
Также мы задаем выражение для сэмплирования в виде хэша по идентификатору посетителя. Это позволяет псевдослучайным образом перемешать данные в таблице для каждого `CounterID` и `EventDate`. Если при выборке данных задать секцию [SAMPLE](../../query_language/select.md#select-section-sample) то ClickHouse вернёт равномерно-псевдослучайную выборку данных для подмножества посетителей.
|
||
|
||
`index_granularity` можно было не указывать, поскольку 8192 — это значение по умолчанию.
|
||
|
||
### Устаревший способ конфигурирования движка
|
||
|
||
!!!attention
|
||
Не используйте этот способ в новых проектах и по возможности переведите старые проекты на способ описанный выше.
|
||
|
||
```
|
||
ENGINE [=] MergeTree(date-column [, sampling_expression], (primary, key), index_granularity)
|
||
```
|
||
|
||
**Параметры MergeTree()**
|
||
|
||
- `date-column` — имя столбца с типом [Date](../../data_types/date.md#data_type-date). На основе этого столбца ClickHouse автоматически создаёт партиции по месяцам. Имена партиций имеют формат `"YYYYMM"`.
|
||
- `sampling_expression` — выражение для сэмплирования.
|
||
- `(primary, key)` — первичный ключ. Тип — [Tuple()](../../data_types/tuple.md#data_type-tuple). Может состоять из произвольных выражений, но обычно это кортеж столбцов. Обязательно должен включать в себя выражение для сэмплирования, если оно задано. Не обязан включать в себя столбец с датой `date-column`.
|
||
- `index_granularity` — гранулярность индекса. Число строк данных между «засечками» индекса. Для большинства задач подходит значение 8192.
|
||
|
||
**Пример**
|
||
|
||
```
|
||
MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192)
|
||
```
|
||
|
||
Движок `MergeTree` сконфигурирован таким же образом, как и в примере выше для основного способа конфигурирования движка.
|
||
|
||
## Хранение данных
|
||
|
||
Таблица хранится блоками данных, отсортированных по первичному ключу. Каждый блок маркируется максимальной и минимальной датами хранящихся в нём записей.
|
||
|
||
При вставке в таблицу создаются отдельные блоки данных, каждый из которых лексикографически отсортирован по первичному ключу. Например, если первичный ключ — `(CounterID, Date)`, то данные в блоке будут лежать в порядке `CounterID`, а для каждого `CounterID` в порядке `Date`.
|
||
|
||
Данные, относящиеся к разным месяцам разбиваются на разные блоки. В дальнейшем ClickHouse в фоновом режиме объединяет мелкие блоки в более крупные для более эффективного хранения. Блоки, относящиеся к разным месяцам не объединяются, это локализует модификации и упрощает бэкапы. Поддерживается запрос `OPTIMIZE`, который вызывает один внеочередной шаг слияния.
|
||
|
||
Для каждого блока данных ClickHouse создаёт индексный файл, который содержит значение первичного ключа для каждой индексной строки («засечка»). Номера индексных строк определяются как `n * index_granularity`, а максимальное значение `n` равно целой части от деления общего количества строк на `index_granularity`. Для каждого столбца также пишутся «засечки» для тех же индексных строк, что и для первичного ключа, эти «засечки» позволяют находить непосредственно данные в столбцах.
|
||
|
||
Вы можете использовать одну большую таблицу, постоянно добавляя в неё данные небольшими пачками, именно для этого предназначен движок `MergeTree`.
|
||
|
||
## Первичные ключи и индексы в запросах
|
||
|
||
Рассмотрим первичный ключ — `(CounterID, Date)`, в этом случае, сортировку и индекс можно проиллюстрировать следующим образом:
|
||
|
||
```
|
||
Whole data: [-------------------------------------------------------------------------]
|
||
CounterID: [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
|
||
Date: [1111111222222233331233211111222222333211111112122222223111112223311122333]
|
||
Marks: | | | | | | | | | | |
|
||
a,1 a,2 a,3 b,3 e,2 e,3 g,1 h,2 i,1 i,3 l,3
|
||
Marks numbers: 0 1 2 3 4 5 6 7 8 9 10
|
||
```
|
||
|
||
Если в запросе к данным указать:
|
||
|
||
- `CounterID IN ('a', 'h')`, то сервер читает данные в диапазонах засечек `[0, 3)` и `[6, 8)`.
|
||
- `CounterID IN ('a', 'h') AND Date = 3`, то сервер читает данные в диапазонах засечек `[1, 3)` и `[7, 8)`.
|
||
- `Date = 3`, то сервер читает данные в диапазоне засечек `[1, 10)`.
|
||
|
||
Примеры выше показывают, что использование индекса всегда эффективнее, чем full scan.
|
||
|
||
Разреженный индекс допускает чтение лишних строк. При чтении одного диапазона первичного ключа, может быть прочитано до `index_granularity * 2` лишних строк в каждом блоке данных. В большинстве случаев ClickHouse не теряет производительности при `index_granularity = 8192`.
|
||
|
||
Разреженность индекса позволяет работать даже с очень большим количеством строк в таблицах, поскольку такой индекс всегда помещается в оперативную память компьютера.
|
||
|
||
ClickHouse не требует уникального первичного ключа. Можно вставить много строк с одинаковым первичным ключом.
|
||
|
||
### Выбор первичного ключа
|
||
|
||
Количество столбцов в первичном ключе не ограничено явным образом. В зависимости от структуры данных в первичный ключ можно включать больше или меньше столбцов. Это может:
|
||
|
||
- Увеличить эффективность индекса.
|
||
|
||
Пусть первичный ключ — `(a, b)`, тогда добавление ещё одного столбца `c` повысит эффективность, если выполнены условия:
|
||
|
||
- Есть запросы с условием на столбец `c`.
|
||
- Часто встречаются достаточно длинные (в несколько раз больше `index_granularity`) диапазоны данных с одинаковыми значениями `(a, b)`. Иначе говоря, когда добавление ещё одного столбца позволит пропускать достаточно длинные диапазоны данных.
|
||
|
||
- Улучшить сжатие данных.
|
||
|
||
ClickHouse сортирует данные по первичному ключу, поэтому чем выше однородность, тем лучше сжатие.
|
||
|
||
- Обеспечить дополнительную логику при слиянии в движках [CollapsingMergeTree](collapsingmergetree.md#table_engine-collapsingmergetree) и [SummingMergeTree](summingmergetree.md#table_engine-summingmergetree).
|
||
|
||
Может потребоваться иметь много полей в первичном ключе, даже если они не нужны для выполнения предыдущих пунктов.
|
||
|
||
Длинный первичный ключ будет негативно влиять на производительность вставки и потребление памяти, однако на производительность ClickHouse при запросах `SELECT` лишние столбцы в первичном ключе не влияют.
|
||
|
||
### Использование индексов и партиций в запросах
|
||
|
||
Для запросов `SELECT` ClickHouse анализирует возможность использования индекса. Индекс может использоваться, если в секции `WHERE/PREWHERE`, в качестве одного из элементов конъюнкции, или целиком, есть выражение, представляющее операции сравнения на равенства, неравенства, а также `IN` или `LIKE` с фиксированным префиксом, над столбцами или выражениями, входящими в первичный ключ или ключ партиционирования, либо над некоторыми частично монотонными функциями от этих столбцов, а также логические связки над такими выражениями.
|
||
|
||
Таким образом, обеспечивается возможность быстро выполнять запросы по одному или многим диапазонам первичного ключа. Например, в указанном примере будут быстро работать запросы для конкретного счётчика; для конкретного счётчика и диапазона дат; для конкретного счётчика и даты, для нескольких счётчиков и диапазона дат и т. п.
|
||
|
||
Рассмотрим движок сконфигурированный следующим образом:
|
||
|
||
```
|
||
ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate) SETTINGS index_granularity=8192
|
||
```
|
||
|
||
В этом случае в запросах:
|
||
|
||
```sql
|
||
SELECT count() FROM table WHERE EventDate = toDate(now()) AND CounterID = 34
|
||
SELECT count() FROM table WHERE EventDate = toDate(now()) AND (CounterID = 34 OR CounterID = 42)
|
||
SELECT count() FROM table WHERE ((EventDate >= toDate('2014-01-01') AND EventDate <= toDate('2014-01-31')) OR EventDate = toDate('2014-05-01')) AND CounterID IN (101500, 731962, 160656) AND (CounterID = 101500 OR EventDate != toDate('2014-05-01'))
|
||
```
|
||
|
||
ClickHouse будет использовать индекс по первичному ключу для отсечения не подходящих данных, а также ключ партиционирования по месяцам для отсечения партиций, которые находятся в не подходящих диапазонах дат.
|
||
|
||
Запросы выше показывают, что индекс используется даже для сложных выражений. Чтение из таблицы организовано так, что использование индекса не может быть медленнее, чем full scan.
|
||
|
||
В примере ниже индекс не может использоваться.
|
||
|
||
```sql
|
||
SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'
|
||
```
|
||
|
||
Чтобы проверить, сможет ли ClickHouse использовать индекс при выполнении запроса, используйте настройки [force_index_by_date](../settings/settings.md#settings-settings-force_index_by_date) и [force_primary_key](../settings/settings.md#settings-settings-force_primary_key).
|
||
|
||
Ключ партиционирования по месяцам обеспечивает чтение только тех блоков данных, которые содержат даты из нужного диапазона. При этом блок данных может содержать данные за многие даты (до целого месяца). В пределах одного блока данные упорядочены по первичному ключу, который может не содержать дату в качестве первого столбца. В связи с этим, при использовании запроса с указанием условия только на дату, но не на префикс первичного ключа, будет читаться данных больше, чем за одну дату.
|
||
|
||
|
||
## Конкурентный доступ к данным
|
||
|
||
Для конкурентного доступа к таблице используется мультиверсионность. То есть, при одновременном чтении и обновлении таблицы, данные будут читаться из набора кусочков, актуального на момент запроса. Длинных блокировок нет. Вставки никак не мешают чтениям.
|
||
|
||
Чтения из таблицы автоматически распараллеливаются.
|