INSERT ... VALUES,INSERT ... SELECT 同样可以通过异步插入队列执行。本页介绍它在什么情况下会走这条路径、写入端如何统计,以及查询被取消或超时后会发生什么。
适用条件与路由
只有在同时满足以下所有条件时,INSERT ... SELECT 才会走异步队列路径:
async_insert = 1。async_insert_select_as_async_insert = 1(默认值) 。这是总开关:关闭后,无论async_insert如何设置,INSERT ... SELECT始终同步执行。将compatibility设置为 26.10 之前的版本会使其默认值变为0;显式设置async_insert_select_as_async_insert = 1即可重新启用。- 整个
SELECT结果为单个块,且大小不超过async_insert_max_data_size。 - 目标端是
MergeTree家族的表 (进行此检查时,别名会被解析为其实际指向的目标) 。
- 结果跨越多个块、超过
async_insert_max_data_size,或为空。 - 使用了非并行 quorum:设置了
insert_quorum且insert_quorum_parallel = 0。 - 目标端存在依赖的 materialized view。
- 目标端本身是 materialized view。
MergeTree检查针对的是该视图本身,而非其 target table,因此不符合条件。 - 目标端是表函数,或
Distributed等远程表引擎。 SELECT读取的正是其自身的目标表 (INSERT INTO t SELECT ... FROM t) 。insert_null_as_default需要为写入非Nullable目标列的NullableSELECT列替换默认值。- 插入为内部操作 (可刷新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 (默认开启) 时,从 s3Cluster 等集群数据源读取并写入复制表的 INSERT ... SELECT 会走分布式插入路径,根本不会进入异步队列。并行副本的情况同样如此。
返回模式与等待
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(以及 HTTPX-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,或某项授权被撤销,都会影响此次刷写。