Содержание
    04.11.2025

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

    Почему не стоит использовать ClickHouse как универсальную БД

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

    Подключение к ClickHouse

    ClickHouse поддерживает несколько протоколов для взаимодействия с базой данных, каждый протокол имеет свой отдельный порт. Кроме этого база данных имеет веб интерфейс для выполнения запросов. 

    Ссылка на веб интерфейс выполнения запросов
    Ссылка на веб интерфейс выполнения запросов

    Типы таблиц в ClickHouse

    Изучение ClickHouse стоит начинать с семейства таблиц MergeTree. Их поведение наиболее схоже с классическими СУБД — строки просто добавляются в конец таблицы. Затем можно переходить к SummingMergeTree, а при необходимости более сложной агрегации — к AggregatingMergeTree.

    Таблица SummingMergeTree

    Для сбора статистики, где данные нужно суммировать, подойдёт тип SummingMergeTree. Такие таблицы автоматически агрегируют числовые поля с группировкой по первичному ключу (указанному в поле ORDER BY).

    CREATE TABLE stat_ip (
      date Date, 
      ip IPv6, 
      requests UInt64
    ) ORDER BY (date, ip)

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

    INSERT INTO stat_ip (date, ip, requests) 
    VALUES 
      ('2025-11-16', '127.0.0.1', 200),
      ('2025-11-16', '127.0.0.1', 100),
      ('2025-11-16', '127.0.0.1', 300),
      ('2025-11-16', '127.0.0.1', 500)
    dateiprequests
    2025-11-16127.0.0.11100

    Условие ORDER BY при создании таблицы аналогично PRIMARY KEY в MySQL, за исключением того, что в MergeTree-таблицах оно не обязано быть уникальным.

    В SummingMergeTree поля из ORDER BY используются как ключ агрегации.

    Как правильно выбирать данные из SummingMergeTree

    Так как сразу после вставки строки ещё могут не быть агрегированы, при выборках рекомендуется явно указывать GROUP BY и SUM(): 

    SELECT
      date,
      ip,
      SUM(requests) AS req
    FROM stat_ip
    GROUP BY date, ip

    Без использования GROUP BY можно получить несколько строк с одинаковым ключом, если агрегация внутри таблицы ещё не выполнена.

    Сжатие данных в таблицах

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

    Типы данных

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

    • Целые типы данных имеют такие жа название как и в MySQL. Точнее они имеют вид Int8 - Int256, UInt8 - UInt256, но поддерживаются и алиасы, аналогичные типам данных в MySQL.
    • Char(x) - FixedString(x) - использовать нужно только если длина сохраняемого в поле текста всегда одинакова. Если добавить более короткий текст, то данные будут дополнены NULL символом \0.
    • Для хранения varchar, text, blob используется единый тип String.
    • Enum поля имеют такой же синтаксис, как и в MySQL.
    • IPv6 используется для хранения IP обоих версий. IPv4 автоматически преобразуется к формату IPv6: 127.0.0.1 → ::ffff:127.0.0.1

    При переносе запросов и статистики из MySQL в ClickHouse важно учитывать несколько ключевых различий в синтаксисе и логике выполнения:

    Удаление устаревших данных

    В ClickHouse можно указать срок хранения данных в параметре TTL прямо при создании таблицы. Например, если вам нужны данные только за последние 2 месяца, нет необходимости создавать отдельный скрипт для очистки — система сама удалит устаревшие строки.

    CREATE TABLE log (
      dtime DateTime,
      ip IPv6,
      url String
    ) 
    ORDER BY (dtime)
    TTL dtime + INTERVAL 2 MONTH; -- хранить данные только 2 месяца, затем удалять
    
    -- Изменить в существующей таблице срок хранения данных 
    ALTER TABLE log TTL dtime + INTERVAL 2 MONTH;
    

    Удаление записей из таблицы

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

    ALTER TABLE stat_ip DELETE WHERE date < '2025-10-01';
    
    -- вместо стандартного 
    
    DELETE FROM stat_ip WHERE date < '2025-10-01';

    Вставка данных в таблицу

    ClickHouse не поддерживает формат вставки INSERT ... SET

    -- MySQL
    INSERT INTO table
    SET
      ip = '8.8.8.8',
      name = 'Google DNS';
    
    -- ClickHouse
    INSERT INTO table (ip, name) VALUES ('8.8.8.8', 'Google DNS');

    Сегментирование таблицы

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

    CREATE TABLE stat_ip (
      date Date,
      ip IPv6,
      requests UInt32
    ) ORDER BY (date, ip)
    PARTITION BY toYYYYMMDD(date) -- сегментирование по дням

    Наиболее популярные функции для определения сегментов: toYYYYMMDD(), toYYYYMM()

    Форматирование даты

    В ClickHouse вместо функции DATE_FORMAT() используется аналог — formatDateTime().

    -- MySQL
    DATE_FORMAT(created_at, '%Y-%m-%d')
    
    -- ClickHouse
    formatDateTime(created_at, '%Y-%m-%d')

    Фильтрация вычисляемых полей

    В ClickHouse поля, которые вычисляются в блоке SELECT, можно фильтровать только в блоке HAVING. В MySQL те же выражения часто допускается использовать в WHERE, но в ClickHouse это приведёт к ошибке.

    -- MySQL
    SELECT user_id, COUNT(*) AS cnt
    FROM events
    WHERE cnt > 10
    GROUP BY user_id;
    
    -- ClickHouse
    SELECT user_id, COUNT(*) AS cnt
    FROM events
    GROUP BY user_id
    HAVING cnt > 10;
    

    Использование агрегирующих функций

    Если в запросе присутствует GROUP BY, то все поля, указанные в SELECT, но не перечисленные в GROUP BY, должны быть обёрнуты в агрегирующую функцию. Если вы хотите вывести значение поля без агрегации, используйте функцию any().

    -- MySQL
    SELECT user_id, name, COUNT(*) FROM users GROUP BY user_id;
    
    -- ClickHouse
    SELECT user_id, any(name), COUNT(*) FROM users GROUP BY user_id;

    Регистр символов в названиях функций

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

    -- Запрос выдающий ошибку так как название функции md5() должно быть в верхнем регистре
    CREATE TABLE test (
      name String,
      idx FixedString(32) MATERIALIZED md5(name)
    )
    
    -- Корректный вариант
    CREATE TABLE test (
      name String,
      idx FixedString(32) MATERIALIZED MD5(name)
    )

    GROUP_CONCAT(DISTINCT ...)

    ClickHouse умеет работать с массивами, поэтому для построения запроса удобней использовать конструкцию toJSONString(groupArrayDistinct(ip)) которая вернет сгруппированные строки, без дублирующихся значений в виде JSON

    -- MySQL
    SELECT user, GROUP_CONCAT(DITINCT ip) FROM sessions GROUP BY user
    
    -- ClickHouse
    SELECT user, toJSONString(groupArrayDistinct(ip)) FROM sessions GROUP BY user

    Хранение IPv4 совместно с IPv6

    Для хранения IPv4 и IPv6 адресов, в ClickHouse используется тип данных IPv6. В такое поле можно добавлять и IPv4 адреса. В результатах запроса IPv4 адреса имеют префикс ::ffff:, например ::ffff:10.1.0.2 Избавится от него можно следующим условием:

    SELECT if(startsWith(toString(ip), '::ffff:'), toString(toIPv4(ip)), toString(ip))
    FROM apache_log