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

- Почему ClickHouse не любит частые точечные обновления
- ALTER TABLE UPDATE
- ALTER TABLE DELETE
- TTL для автоматического удаления старых данных
- ReplacingMergeTree для последней версии строки
- Как следить за мутациями
- RENAME TABLE
- Когда что выбрать
- Безопасный сценарий массового исправления
- Когда лучше пересобрать таблицу
- Что продумать перед изменением данных
- Почему сначала считают строки, а не запускают UPDATE?
- Когда лучше не мутировать таблицу?
- Как не превратить ClickHouse в неудобную OLTP-базу?
Почему 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 и зачем нужна эта база данных



