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

Описание

Полная официальная спецификация формата Native доступна здесь, а сопутствующая спецификация протокола Native — TCP-протокола, по которому он передается, — доступна здесь.
Обе спецификации были сгенерированы LLM на основе исходного кода ClickHouse. Код остается основным источником истины: если спецификация и код расходятся, верен код.
Формат Native — самый эффективный формат в ClickHouse, поскольку он действительно является «столбцовым»: он не преобразует столбцы в строки. В этом формате данные записываются и читаются блоками в бинарном формате. Для каждого блока последовательно записываются количество строк, количество столбцов, имена и типы столбцов, а также части столбцов в блоке. Этот формат используется в нативном интерфейсе для взаимодействия между серверами, в клиенте командной строки и в клиентах на C++.
Этот формат можно использовать для быстрого создания дампов, которые может читать только СУБД ClickHouse. Однако работать с этим форматом напрямую не всегда удобно.

Формат передачи данных для типов данных

Данные передаются в столбцовом формате: каждый столбец передаётся отдельно, а все его значения отправляются вместе как единый массив. Каждый столбец в блоке содержит заголовок, аналогичный RowBinaryWithNamesAndTypes.
При использовании нативного бинарного протокола TCP (или когда конечная точка HTTP получает ?client_protocol_version=<n>), структура BlockInfo записывается перед количеством столбцов и строк. В примерах этого раздела используется обычный HTTP-интерфейс без версии протокола, поэтому BlockInfo не записывается.

Структура блока

Следующий запрос возвращает два столбца, number и str, из трёх строк:
Выходные данные помещаются в один блок ClickHouse и будут выглядеть так:

Несколько блоков

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

Простые типы данных

Формат передачи данных отдельного значения одного из простых типов данных аналогичен RowBinary/RowBinaryWithNamesAndTypes. Полный список типов, соответствующих этому описанию:
  • (U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
  • Float32, Float64
  • Bool
  • String
  • FixedString(N)
  • Date
  • Date32
  • DateTime
  • DateTime64
  • IPv4
  • IPv6
  • UUID
Подробнее см. описания перечисленных выше типов в разделе “Формат передачи данных типов данных RowBinary”.

Сложные типы данных

Кодирование следующих типов отличается от кодирования в RowBinary и RowBinaryWithNamesAndTypes.
  • Nullable
  • LowCardinality
  • Array
  • Map
  • Variant
  • Dynamic
  • JSON

Nullable

В формате Native перед самими данными для столбца с типом Nullable записывается количество байтов, равное числу строк в блоке. Каждый из этих байтов указывает, равно ли значение NULL или нет. Например, в этом запросе каждое нечётное число будет NULL:
Результат будет выглядеть так:
С Nullable(String) это работает аналогично. Индикатор NULL всегда задаётся байтом маски nullable — значение маски 0x01 означает, что строка равна NULL независимо от содержимого строки. Для строк со значением NULL базовая строка хранится как пустая строка (длина LEB128 0). Обратите внимание, что пустая строка с не-NULL значением также имеет длину LEB128 0, поэтому эти два случая различаются только байтом маски. Например, следующий запрос:
Результат будет выглядеть так:

LowCardinality

В отличие от RowBinary, где LowCardinality прозрачен, формат Native использует словарное столбцовое кодирование. Столбец кодируется как префикс версии, затем словарь уникальных значений и массив целочисленных индексов этого словаря.
Столбец можно определить как LowCardinality(Nullable(T)), но определить его как Nullable(LowCardinality(T)) нельзя — это всегда приводит к ошибке сервера.
Префикс версии — это UInt64(LE) со значением 1, который записывается один раз для каждого столбца. Затем для каждого блока записывается следующее:
  • UInt64(LE) — битовое поле IndexesSerializationType. Биты 0–7 кодируют ширину индекса (0 = UInt8, 1 = UInt16, 2 = UInt32, 3 = UInt64). Бит 8 (NeedGlobalDictionaryBit) никогда не устанавливается в формате Native (сервер генерирует исключение, если он встречается). Бит 9 указывает на наличие дополнительных ключей словаря. Бит 10 указывает, что словарь нужно сбросить.
  • UInt64(LE) — количество ключей словаря, после которого сами ключи массово сериализуются с использованием кодирования внутреннего типа.
  • UInt64(LE) — количество строк, после которого значения индексов массово сериализуются с использованием соответствующей разрядности UInt.
Словарь всегда содержит значение по умолчанию по индексу 0 (например, пустую строку для String, 0 для числовых типов). Для LowCardinality(Nullable(T)) индекс 0 соответствует NULL, а ключи сериализуются без обёртки Nullable. Например, LowCardinality(String) с 5 строками ['foo', 'bar', 'baz', 'foo', 'bar']:
В LowCardinality(Nullable(String)) индекс 0 — NULL:

Array

В отличие от RowBinary, где каждому массиву предшествует число элементов в кодировке LEB128, формат Native кодирует массивы как два столбцовых подпотока:
  • N накопительных смещений UInt64 (little-endian, по 8 байт каждое). Строка i содержит offset[i] - offset[i-1] элементов, при этом offset[-1] неявно считается равным 0.
  • Все вложенные элементы из всех строк, сериализованные подряд в один непрерывный блок.
Например, Array(UInt32) с 3 строками [[0, 10], [1, 11], [2, 12]]:
Пустой массив имеет то же смещение, что и в предыдущей строке. Например, Array(String) с 4 строками [[], ['0'], ['0','1'], ['0','1','2']]:

Map

Map(K, V) кодируется как Array(Tuple(K, V)) — сначала идут смещения массива, затем все ключи, а потом все значения. Это отличается от RowBinary, где ключи и значения чередуются в каждой записи. Например, Map(String, UInt64) с 3 строками [{'a':0,'b':10}, {'a':1,'b':11}, {'a':2,'b':12}]:

Variant

В отличие от RowBinary, где каждая строка содержит собственный байт дискриминатора, за которым сразу следует встроенное значение, формат Native отделяет дискриминаторы от данных.
Как и в RowBinary, типы в определении всегда сортируются по алфавиту, а дискриминатор — это индекс в этом отсортированном списке. 0xFF (255) обозначает NULL.
Столбец Variant кодируется следующим образом:
  • Префикс режима дискриминаторов UInt64(LE) (0 = BASIC, 1 = COMPACT). Вывод в формате Native обычно использует BASIC (0); режим COMPACT может встречаться при чтении данных, сохраненных с включенным use_compact_variant_discriminators_serialization.
  • N дискриминаторов UInt8, по одному на строку.
  • Данные каждого типа варианта в виде отдельного столбца с массовыми данными, содержащего только соответствующие строки, в порядке дискриминаторов.
Например, Variant(String, UInt32) с 5 строками [0::UInt32, 'hello', NULL, 3::UInt32, 'hello'] (в отсортированном списке: String = 0, UInt32 = 1):

Dynamic

В отличие от RowBinary, где каждое значение является самоописывающимся (префикс типа + значение), формат Native сериализует Dynamic как префикс структуры, за которым следует столбец Variant. Префикс структуры содержит версию сериализации UInt64(LE), затем количество динамических типов (в формате VarUInt), а затем имена типов в виде строк. В версии V1 количество типов для совместимости записывается дважды. Следующие за ним данные представляют собой столбец Variant, список типов которого включает динамические типы и внутренний тип SharedVariant, отсортированные по алфавиту. Например, Dynamic с 5 строками [0::UInt32, 'hello', NULL, 3::UInt32, 'hello']:

JSON

В отличие от RowBinary, где каждая строка содержит имена путей и значения и потому является самоописывающейся, формат Native сериализует JSON в столбцовой структуре. Схема кодирования сложна и зависит от версии: она включает префикс структуры с версией сериализации, имена динамических путей и структуру общих данных, после чего следуют типизированные пути (каждый в виде отдельного столбца), динамические пути (каждый как столбец Dynamic) и общие данные для overflow-путей. Для более простой совместимости можно использовать настройку output_format_native_write_json_as_string=1, которая сериализует JSON-столбцы как обычные текстовые строки JSON (по одному String на строку).
Последнее изменение 2 июля 2026 г.