> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Асинхронный INSERT ... SELECT

> Как INSERT ... SELECT проходит через очередь асинхронных вставок, а также какие правила маршрутизации, учёта и отмены к нему применяются.

Через очередь [асинхронных вставок](/docs/ru/optimize/asynchronous-inserts) может проходить не только обычный `INSERT ... VALUES`, но и `INSERT ... SELECT`. На этой странице описано, в каких случаях это происходит, как учитывается запись и что происходит, если запрос отменён или истёк его тайм-аут.

<h2 id="eligibility-and-routing">
  Условия применения и выбор маршрута
</h2>

Запрос `INSERT ... SELECT` направляется в асинхронную очередь, только если выполняются все следующие условия:

* `async_insert = 1`.
* `async_insert_select_as_async_insert = 1` (значение по умолчанию). Это главный переключатель: если он выключен, `INSERT ... SELECT` всегда выполняется синхронно независимо от `async_insert`. Если задать для `compatibility` версию ниже 26.10, его значение по умолчанию меняется на `0`; чтобы снова включить эту возможность, явно укажите `async_insert_select_as_async_insert = 1`.
* Весь результат `SELECT` умещается в один блок размером не более `async_insert_max_data_size`.
* Пункт назначения — таблица семейства `MergeTree` (при этой проверке псевдоним разрешается в свою целевую таблицу).

Даже если все перечисленные условия выполнены, вставка переключается на синхронный маршрут в любом из следующих случаев:

* Результат занимает более одного блока, превышает `async_insert_max_data_size` или пуст.
* Используется непараллельный кворум: задан `insert_quorum` и `insert_quorum_parallel = 0`.
* У пункта назначения есть зависимые materialized view.
* Пункт назначения является materialized view. Проверка на `MergeTree` применяется к самому представлению, а не к его целевой таблице, поэтому условие не выполняется.
* Пункт назначения — табличная функция или удалённый движок таблицы, например `Distributed`.
* `SELECT` читает данные из собственной целевой таблицы (`INSERT INTO t SELECT ... FROM t`).
* `insert_null_as_default` должен подставлять значение по умолчанию для столбца `SELECT` типа `Nullable`, данные которого записываются в целевой столбец, не являющийся `Nullable`.
* Вставка является внутренней (обновление refreshable materialized view, `POPULATE` или `CREATE TABLE ... AS SELECT`). Такие вставки всегда выполняются синхронно и игнорируют `async_insert_select_as_async_insert` и `async_insert`.

С транзакциями ситуация иная. Если запрос выполняется внутри транзакции (явной или при `implicit_transaction = 1`), асинхронный путь не поддерживается, и по умолчанию ClickHouse не переключается на синхронный маршрут, а генерирует ошибку `NOT_IMPLEMENTED`. Запрос переключается на синхронную вставку, только если задано `throw_on_unsupported_query_inside_transaction = 0`.

Маршрут, выбранный до проверки возможности асинхронной вставки, имеет приоритет, поэтому сторона `SELECT` может сама исключить асинхронный путь. Если включён `parallel_distributed_insert_select` (по умолчанию включён), запрос `INSERT ... SELECT`, который читает данные из кластерного источника, например `s3Cluster`, и записывает их в реплицируемую таблицу, выполняется по пути распределённой вставки и никогда не попадает в асинхронную очередь. То же самое происходит при использовании параллельных реплик.

<h2 id="return-mode-and-waiting">
  Режим возврата и ожидание
</h2>

`wait_for_async_insert` работает так же, как и для любой асинхронной вставки: при `1` (значение по умолчанию) запрос ожидает сброса данных на диск; при `0` он возвращает управление сразу после постановки блока в очередь.

Чтобы `SELECT` сформировал единственный блок, необходимый для асинхронного пути, используйте [`optimize_trivial_insert_select`](/docs/ru/reference/settings/session-settings/optimize-trivial#optimize_trivial_insert_select): эта настройка ограничивает число потоков чтения для тривиального запроса `INSERT INTO dest SELECT ... FROM source` между разными таблицами значением [`max_insert_threads`](/docs/ru/reference/settings/session-settings/max-insert#max_insert_threads). Если установить `max_insert_threads = 1`, `SELECT`, как правило, сформирует один блок.

<h2 id="write-accounting-and-observability">
  Учёт записи и обсервабилити
</h2>

Запись учитывается за запросом сброса на диск, а не за исходным `INSERT ... SELECT`:

* Метрики записи `ProfileEvents` (`InsertedRows`, `InsertedBytes`) относятся к запросу сброса на диск, и для него в `system.query_log` создаётся отдельная строка, описывающая запись. В строке исходного `INSERT ... SELECT` для этих `ProfileEvents` указано `0`. Чтобы увидеть показатели записи самого сброса, посмотрите его строку в `system.query_log` и `system.asynchronous_insert_log`.
* При `wait_for_async_insert = 1` (значение по умолчанию) собственные `written_rows` и `written_bytes` запроса (а также HTTP-заголовок `X-ClickHouse-Summary`) отражают строки и байты, принятые из `SELECT`, а не итоговые показатели запроса сброса. Если сброс на диск завершится неудачно, клиент всё равно получит ошибку.
* При `wait_for_async_insert = 0` эти показатели не возвращаются, поскольку запрос завершается раньше, чем становится известен результат сброса на диск.
* Квота `WRITTEN_BYTES` списывается один раз — на этапе сброса на диск — и не восстанавливается, если сброс завершается с ошибкой.

<h2 id="cancellation-and-timeouts">
  Отмена и тайм-ауты
</h2>

Блок помещается в очередь ещё до начала ожидания сброса на диск, поэтому отмена запроса лишь прекращает ожидание на стороне клиента, но не останавливает сброс очереди на диск.

* `KILL QUERY` или достижение `max_execution_time` во время ожидания приводит к возврату ошибки клиенту, однако строки всё равно могут быть записаны. Отменённый или прерванный по тайм-ауту запрос на этом пути не означает, что данные не были записаны.
* При `timeout_overflow_mode = 'break'` ошибки нет и досрочного возврата тоже: запрос продолжает ждать сброса на диск и после истечения `max_execution_time` — вплоть до `wait_for_async_insert_timeout`. Досрочный выход означал бы сообщение об успехе ещё до подтверждения записи.

<h2 id="when-decisions-are-bound">
  Когда фиксируются решения
</h2>

Возможность использования асинхронного пути определяется один раз — при запуске запроса, до постановки блока в очередь, а предварительная проверка прав доступа отклоняет неавторизованную вставку на раннем этапе. Пойдёт ли `INSERT ... SELECT` по асинхронному пути вообще, зависит от структуры блока результата, а не только от `async_insert`.

Поиск пункта назначения и проверка авторизации привязанных параметров выполняются уже в момент сброса на диск — так же, как и для [любой асинхронной вставки](/docs/ru/optimize/asynchronous-inserts): если между постановкой в очередь и сбросом на диск к пункту назначения будет подключено materialized view или будет отозван grant, это повлияет на сброс.
