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, это повлияет на сброс.