UPDATE, DELETE, TTL и ReplacingMergeTree в ClickHouse

В ClickHouse можно обновлять и удалять данные, но это не значит, что его нужно использовать как обычную OLTP-базу. UPDATE и DELETE здесь выполняются через мутации, а для аналитических сценариев часто лучше подходят append-only модель, TTL, ReplacingMergeTree и пересборка витрин

UPDATE, DELETE, TTL и ReplacingMergeTree в ClickHouse: ключевой визуальный блок

Почему ClickHouse не любит частые точечные обновления

ClickHouse оптимизирован под быструю вставку больших пачек и чтение больших объемов данных. Данные в MergeTree хранятся частями. Когда вы обновляете или удаляете строки, ClickHouse должен изменить соответствующие части. На больших таблицах это может быть тяжелой фоновой операцией

Если приложение постоянно меняет отдельные записи, лучше держать транзакционную часть в PostgreSQL/MySQL, а ClickHouse использовать для истории, событий и отчетов

ALTER TABLE UPDATE

ALTER TABLE demo.events
UPDATE amount = 0
WHERE event_type = 'test';

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

ALTER TABLE DELETE

ALTER TABLE demo.events
DELETE WHERE event_date < today() - 90;

Так можно удалить старые или ошибочные данные. Но для регулярного удаления истории лучше использовать TTL: он описывает политику хранения прямо в таблице

TTL для автоматического удаления старых данных

CREATE TABLE demo.logs
(
    event_time DateTime,
    level LowCardinality(String),
    message String
)
ENGINE = MergeTree
ORDER BY (toDate(event_time), level)
TTL event_time + INTERVAL 90 DAY DELETE;

TTL не срабатывает как секундомер ровно в момент наступления срока. Удаление связано с фоновыми процессами MergeTree. Для политики хранения это нормально, для мгновенной реакции — нет

ReplacingMergeTree для последней версии строки

Если данные приходят как новые версии одного объекта, можно использовать ReplacingMergeTree. Он не обновляет строку на месте, а позволяет хранить несколько версий и при слияниях оставлять актуальную

CREATE TABLE demo.user_state
(
    user_id UInt64,
    version UInt64,
    plan LowCardinality(String),
    updated_at DateTime
)
ENGINE = ReplacingMergeTree(version)
ORDER BY user_id;
INSERT INTO demo.user_state VALUES
(101, 1, 'free', '2026-05-19 10:00:00'),
(101, 2, 'pro', '2026-05-19 11:00:00');

Для чтения "как будто осталась последняя версия" часто используют FINAL, но не стоит ставить его в каждый запрос бездумно: он может быть дорогим. Лучше проектировать запросы и витрины с учетом модели версий

Как следить за мутациями

SELECT
    database,
    table,
    command,
    create_time,
    is_done,
    latest_failed_part,
    latest_fail_reason
FROM system.mutations
ORDER BY create_time DESC;

Если UPDATE или DELETE долго не завершается, смотрите system.mutations. Там видно, выполнена ли мутация и были ли ошибки

RENAME TABLE

RENAME TABLE demo.old_events TO demo.events_archive;

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

Когда что выбрать

ЗадачаПодход
Разово исправить ошибочные строкиALTER TABLE UPDATE
Удалить старые данные по сроку храненияTTL
Удалить ошибочную партиюDELETE WHERE или операции с партициями
Хранить последние версии объектовReplacingMergeTree
Частые транзакционные измененияOLTP-база плюс выгрузка в ClickHouse

Безопасный сценарий массового исправления

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

SELECT count()
FROM demo.events
WHERE event_type = 'test';
SELECT min(event_date), max(event_date)
FROM demo.events
WHERE event_type = 'test';

Если строк много, иногда безопаснее создать новую таблицу через CREATE TABLE ... AS, переложить исправленные данные через INSERT SELECT, проверить результат и затем переименовать таблицы. Это дольше на подготовку, но понятнее и контролируемее для критичных данных

Когда лучше пересобрать таблицу

  • Нужно изменить большую долю строк.
  • Меняется логика ключа сортировки или типы колонок.
  • Ошибка затронула целую партию загрузки.
  • Нужна проверяемая миграция с возможностью отката.

Что продумать перед изменением данных

Почему сначала считают строки, а не запускают UPDATE?

Потому что мутация может затронуть большой объем частей и занять заметное время. Перед исправлением полезно понять диапазон дат, партиции и ключ сортировки; это возвращает к базовому уроку CREATE TABLE в ClickHouse

Когда лучше не мутировать таблицу?

Если меняется большая доля данных, типы колонок или логика сортировки. В таких случаях часто безопаснее собрать новую таблицу через INSERT SELECT, проверить результат и переключить имена. Для расчетных таблиц рядом стоит пересмотреть materialized views

Как не превратить ClickHouse в неудобную OLTP-базу?

Оставьте частые точечные изменения в PostgreSQL, MySQL или другом транзакционном слое, а в ClickHouse отправляйте события, версии и исторические срезы. Если нужно освежить общую границу между OLTP и OLAP, вернитесь к вводному материалу Что такое ClickHouse и зачем нужна эта база данных

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x