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

Проблема

ClickHouse Cloud не поддерживает транзакции из нескольких операторов в традиционном для RDBMS смысле. Это создает две распространенные проблемы:
  1. Атомарность в пределах одной таблицы для пакетной загрузки: распространенный подход — сначала выполнять вставку во временные частичные ключи, затем копировать записи в фактический ключ и удалять временные записи. Такой подход работает плохо — особенно на этапе удаления, который может занимать более 90% общего времени операции.
  2. Согласованность между несколькими таблицами: когда конвейер успешно загружает таблицу A, но завершается с ошибкой на таблице B, таблица A уже зафиксирована, и откатить изменения нельзя. Аналитики, выполняющие запросы к обеим таблицам, видят рассинхронизированные данные.

Предпосылки

ClickHouse гарантирует атомарность на уровне одной вставки в одну партицию: если INSERT выполняется успешно, все строки в этом блоке становятся видимыми; если завершается ошибкой — не видна ни одна. Однако встроенного механизма, позволяющего атомарно зафиксировать данные сразу в нескольких вставках или таблицах, нет. Команды управления партициями (MOVE PARTITION TO TABLE, REPLACE PARTITION, ATTACH PARTITION FROM) работают на уровне метаданных, если исходная и целевая таблицы используют одну и ту же политику хранения. Это означает, что они выполняются практически мгновенно независимо от объёма данных, поэтому они отлично подходят в качестве основы для шаблонов atomic-swap. Вместо того чтобы выполнять вставку напрямую в продукционные таблицы и пытаться устранять последствия сбоев, используйте выделенные staging-таблицы в качестве зоны загрузки. После проверки данных используйте операции на уровне партиций, чтобы атомарно перенести данные в продакшн.

Пошаговое руководство по обеспечению атомарности для одной таблицы

1

Создайте staging-таблицу

Используйте ту же схему, ключ партиционирования, ORDER BY и политику хранения, что и у продукционной таблицы.
CREATE TABLE my_table_staging AS my_table_prod;
2

Вставьте данные в staging-таблицу

Выполняйте вставку в staging-таблицу, а не в продукционную таблицу.
INSERT INTO my_table_staging SELECT ... FROM source;
3

Если вставка завершилась ошибкой, очистите таблицу и повторите попытку

Очистите staging-таблицу и снова запустите загрузку. Данные в продакшн при этом не затрагиваются.
TRUNCATE TABLE my_table_staging;
4

Если вставка прошла успешно, переместите партиции в продакшн

Чтобы переместить данные в продакшн и удалить их из staging-таблицы, используйте MOVE PARTITION.
ALTER TABLE my_table_staging MOVE PARTITION <partition_expr> TO TABLE my_table_prod;
Либо скопируйте данные в существующую партицию в продакшн с помощью ATTACH PARTITION.
ALTER TABLE my_table_prod ATTACH PARTITION tuple() FROM my_table_staging;
Обе операции выполняются на уровне метаданных в рамках одной политики хранения и завершаются почти мгновенно.
5

Очистите staging-таблицу

После перемещения всех партиций очистите staging-таблицу, чтобы она оставалась пустой для следующей загрузки.
TRUNCATE TABLE my_table_staging;

Согласованность между несколькими таблицами

Тот же подход решает проблему конвейеров, в которых две или более таблиц должны быть полностью загружены, прежде чем хоть одна из них станет видимой для аналитиков. Загружайте данные каждой таблицы в её собственную staging-таблицу и проверяйте их все, а затем одновременно выполняйте перемещения партиций. Поскольку каждое такое перемещение — почти мгновенная операция с метаданными, период, в течение которого данные в таблицах могут быть несогласованными, сокращается с длительности полной загрузки до времени, нужного для переключения партиций.

Требования и ограничения

Для MOVE PARTITION TO TABLE, REPLACE PARTITION и ATTACH PARTITION FROM исходная и целевая таблицы должны иметь:
  • Одинаковую структуру столбцов
  • Одинаковый ключ партиционирования, ключ ORDER BY и первичный ключ
  • Одинаковую политику хранения
  • Целевая таблица должна содержать все индексы и проекции из исходной таблицы

Пример

Вы можете интерактивно выполнить приведённый ниже пример с помощью fiddle:
CREATE TABLE prod
(
  uid Int16,
  name String,
  age Int16
)
ENGINE=MergeTree
ORDER BY ();

CREATE TABLE staging
(
  uid Int16,
  name String,
  age Int16
)
ENGINE=MergeTree
ORDER BY ();

-- Начальные данные
INSERT INTO prod VALUES (123, 'John', 33);
INSERT INTO prod VALUES (456, 'Ksenia', 48);
-- Загрузка данных
INSERT INTO staging VALUES (8811, 'Alice', 50);
INSERT INTO staging VALUES (8812, 'Bob', 23);

-- Проверка импорта
SELECT 'Staging count:', COUNT() FROM staging;
-- Перемещение партиции
ALTER TABLE staging MOVE PARTITION tuple() TO TABLE prod; -- атомарная операция

-- Проверка данных
SELECT 'Prod count:', COUNT() FROM prod;
SELECT * FROM prod;

Справка

Последнее изменение 10 июня 2026 г.