> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-fix-nav-issues.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Атомарные вставки и согласованность между несколькими таблицами в ClickHouse Cloud

> Как атомарно загружать данные и сохранять согласованность между несколькими таблицами в ClickHouse Cloud без многооператорных транзакций, используя staging-таблицы и операции на уровне партиций.

<div id="problem">
  ## Проблема
</div>

ClickHouse Cloud не поддерживает транзакции из нескольких операторов в традиционном для RDBMS смысле.
Это создает две распространенные проблемы:

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

<div id="background">
  ## Предпосылки
</div>

ClickHouse гарантирует атомарность на уровне одной вставки в одну партицию: если `INSERT` выполняется успешно, все строки в этом блоке становятся видимыми; если завершается ошибкой — не видна ни одна. Однако встроенного механизма, позволяющего атомарно зафиксировать данные сразу в нескольких вставках или таблицах, нет.

Команды управления партициями ([`MOVE PARTITION TO TABLE`](/ru/reference/statements/alter/partition#move-partition-to-table), [`REPLACE PARTITION`](/ru/reference/statements/alter/partition#replace-partition), [`ATTACH PARTITION FROM`](/ru/reference/statements/alter/partition#attach-partition-from)) работают на уровне метаданных, если исходная и целевая таблицы используют одну и ту же политику хранения.

Это означает, что они выполняются практически мгновенно независимо от объёма данных, поэтому они отлично подходят в качестве основы для шаблонов atomic-swap.

<div id="recommended-solution">
  ## Рекомендуемое решение
</div>

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

<div id="step-by-step">
  ## Пошаговое руководство по обеспечению атомарности для одной таблицы
</div>

<Steps>
  <Step>
    ### Создайте staging-таблицу

    Используйте ту же схему, ключ партиционирования, `ORDER BY` и политику хранения, что и у продукционной таблицы.

    ```sql theme={null}
    CREATE TABLE my_table_staging AS my_table_prod;
    ```
  </Step>

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

    Выполняйте вставку в staging-таблицу, а не в продукционную таблицу.

    ```sql theme={null}
    INSERT INTO my_table_staging SELECT ... FROM source;
    ```
  </Step>

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

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

    ```sql theme={null}
    TRUNCATE TABLE my_table_staging;
    ```
  </Step>

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

    Чтобы переместить данные в продакшн и удалить их из staging-таблицы, используйте `MOVE PARTITION`.

    ```sql theme={null}
    ALTER TABLE my_table_staging MOVE PARTITION <partition_expr> TO TABLE my_table_prod;
    ```

    Либо скопируйте данные в существующую партицию в продакшн с помощью `ATTACH PARTITION`.

    ```sql theme={null}
    ALTER TABLE my_table_prod ATTACH PARTITION tuple() FROM my_table_staging;
    ```

    Обе операции выполняются на уровне метаданных в рамках одной политики хранения и завершаются почти мгновенно.
  </Step>

  <Step>
    ### Очистите staging-таблицу

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

    ```sql theme={null}
    TRUNCATE TABLE my_table_staging;
    ```
  </Step>
</Steps>

<div id="multi-table-consistency">
  ## Согласованность между несколькими таблицами
</div>

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

<div id="requirements-and-constraints">
  ## Требования и ограничения
</div>

Для `MOVE PARTITION TO TABLE`, `REPLACE PARTITION` и `ATTACH PARTITION FROM` исходная и целевая таблицы должны иметь:

* Одинаковую структуру столбцов
* Одинаковый ключ партиционирования, ключ `ORDER BY` и первичный ключ
* Одинаковую политику хранения
* Целевая таблица должна содержать все индексы и проекции из исходной таблицы

<div id="example">
  ## Пример
</div>

Вы можете интерактивно выполнить приведённый ниже пример с помощью [fiddle](https://fiddle.clickhouse.com/7ef9ed84-ac14-4f2c-9ca5-d5913089769a):

```sql theme={null}
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;
```

<div id="references">
  ## Справка
</div>

* [**Управление партициями и частями**](/ru/reference/statements/alter/partition)
* Подробнее об этой стратегии см. в блоге [**Ускорение загрузки больших объёмов данных в ClickHouse — часть 3: как сделать загрузку больших объёмов данных отказоустойчивой**](https://clickhouse.com/blog/supercharge-your-clickhouse-data-loads-part3).
