Skip to main content
Через очередь асинхронных вставок может проходить не только обычный INSERT ... VALUES, но и INSERT ... SELECT. На этой странице описано, в каких случаях это происходит, как учитывается запись и что происходит, если запрос отменён или истёк его тайм-аут.

Условия применения и выбор маршрута

Запрос 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, и записывает их в реплицируемую таблицу, выполняется по пути распределённой вставки и никогда не попадает в асинхронную очередь. То же самое происходит при использовании параллельных реплик.

Режим возврата и ожидание

wait_for_async_insert работает так же, как и для любой асинхронной вставки: при 1 (значение по умолчанию) запрос ожидает сброса данных на диск; при 0 он возвращает управление сразу после постановки блока в очередь. Чтобы SELECT сформировал единственный блок, необходимый для асинхронного пути, используйте optimize_trivial_insert_select: эта настройка ограничивает число потоков чтения для тривиального запроса INSERT INTO dest SELECT ... FROM source между разными таблицами значением max_insert_threads. Если установить max_insert_threads = 1, SELECT, как правило, сформирует один блок.

Учёт записи и обсервабилити

Запись учитывается за запросом сброса на диск, а не за исходным 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 списывается один раз — на этапе сброса на диск — и не восстанавливается, если сброс завершается с ошибкой.

Отмена и тайм-ауты

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

Когда фиксируются решения

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