跳转到主要内容
高效的数据摄取是构建高性能 ClickHouse 部署的基础。选择合适的插入策略会显著影响吞吐量、成本和可靠性。本节将概述最佳实践、利弊权衡以及配置选项,帮助你根据自身工作负载做出正确决策。
以下内容假设你是通过客户端将数据推送到 ClickHouse。如果你是将数据拉取到 ClickHouse,例如使用内置表函数 s3gcs,我们建议参考指南 “优化 S3 插入和读取性能”

默认使用同步插入

默认情况下,向 ClickHouse 执行的插入都是同步的。每个插入查询都会立即在磁盘上创建一个存储数据分片,其中包括元数据和索引。
如果你可以在客户端将数据按批次处理,请使用同步插入如果不能,请参阅下方的异步插入
下面我们简要介绍 ClickHouse 的 MergeTree 插入机制:

客户端侧步骤

为获得最佳性能,数据必须①按批次处理,因此批次大小就成了首要决策 ClickHouse 会将插入的数据存储在磁盘上,并表的主键列排序。因此,第二个决策是是否要在传输到服务器之前②先对数据进行预排序。如果某个批次到达时已经按主键列预先排好序,ClickHouse 就可以跳过⑩排序步骤,从而加快摄取速度。 如果待摄取的数据没有预定义格式,关键决策就是选择哪种格式。ClickHouse 支持以70 多种格式插入数据。不过,在使用 ClickHouse 命令行客户端或编程语言客户端时,这一选择通常会自动完成。如有需要,也可以显式覆盖这一自动选择。 下一个主要决策是④是否在传输到 ClickHouse 服务器之前先压缩数据。压缩可以减小传输体积并提高网络效率,从而加快数据传输速度并降低带宽占用,尤其是在处理大型数据集时。 数据会⑤通过 ClickHouse 网络接口传输——即 native HTTP 接口 (我们将在本文后面对它们进行比较) 。

服务器端步骤

在 ⑥ 收到数据后,如果使用了压缩,ClickHouse 会在 ⑦ 先将其解压,然后在 ⑧ 按原始发送格式进行解析。 ClickHouse 会根据该格式化数据中的值以及目标表的 DDL 语句,在内存中以 MergeTree 格式 ⑨ 构建一个 ;如果这些行尚未预排序,则在 ⑩ 按主键列对其排序;在 ⑪ 创建一个稀疏主索引;在 ⑫ 应用按列压缩;并在 ⑬ 将数据作为新的 ⑭ 数据分片 写入磁盘。

同步时使用批次插入

上述机制表明,无论插入大小如何,都会产生固定开销,因此批次大小是优化摄取吞吐量最重要的因素。对插入进行批处理可以降低这部分开销在总插入耗时中的占比,并提升处理效率。 我们建议每批至少插入 1,000 行数据,理想情况下每批应为 10,000–100,000 行。更少但更大的插入可减少写入的 parts 数量,减轻 merge 负载,并降低整体系统资源消耗。 要让同步插入策略发挥效果,必须在客户端进行这种批处理。 如果你无法在客户端对数据进行批处理,ClickHouse 支持异步插入,可将批处理转移到服务器端 (参见异步插入) 。
无论插入大小如何,我们都建议将插入查询的频率控制在大约每秒一次。原因在于,新创建的 parts 会在后台合并成更大的 parts (以便针对读查询优化数据) ;如果每秒发送过多插入查询,可能会导致后台合并跟不上新 parts 的生成速度。不过,使用异步插入时,你可以采用更高的每秒插入查询频率 (参见异步插入) 。

确保重试具备幂等性

同步插入同样具有幂等性。使用 MergeTree 引擎时,ClickHouse 默认会对插入进行去重。这可以避免以下这类结果不确定的故障情况:
  • 插入已成功,但由于网络中断,客户端始终未收到确认。
  • 插入在服务端失败并超时。
在这两种情况下,重试插入都是安全的——前提是批次内容和顺序保持完全一致。因此,客户端必须以一致的方式重试,不能修改或重排数据。

选择合适的插入目标

对于分片集群,你有两个选择:
  • 直接插入 MergeTreeReplicatedMergeTree 表。如果客户端能够跨分片进行负载均衡,这是效率最高的方案。设置 internal_replication = true 后,ClickHouse 会透明地处理复制。
  • 插入到 分布式表。这样客户端可以将数据发送到任意节点,再由 ClickHouse 将其转发到正确的分片。这种方式更简单,但由于多了一步转发,性能会略低一些。仍建议使用 internal_replication = true
在 ClickHouse Cloud 中,所有节点都会读写同一个分片。插入会自动在各节点之间均衡。你只需将插入请求发送到暴露的端点即可。

选择合适的格式

选择合适的输入格式,对于在 ClickHouse 中高效摄取数据至关重要。ClickHouse 支持 70 多种格式,选择性能最佳的方案会显著影响插入速度、CPU 和内存使用情况,以及整体系统效率。 虽然灵活性对数据工程和基于文件的导入很有帮助,但应用程序应优先选择面向性能的格式
  • Native 格式 (推荐) :效率最高。采用列式格式,服务端几乎无需解析。Go 和 Python 客户端默认使用该格式。
  • RowBinary:高效的行式格式;如果客户端侧难以完成列式转换,它是理想选择。Java 客户端使用该格式。
  • JSONEachRow:易于使用,但解析开销较高。适合低数据量场景或快速集成。

使用压缩

压缩在降低网络开销、加快插入速度以及减少 ClickHouse 存储成本方面至关重要。合理使用压缩,无需更改数据格式或 schema,即可提升摄取性能。 对插入数据进行压缩可以减小通过网络传输的载荷大小,从而减少带宽占用并加快传输速度。 对于插入操作,压缩在与 Native 格式 搭配使用时尤其有效,因为这种格式本身就与 ClickHouse 内部的列式存储模型相匹配。在这种情况下,服务器可以高效解压并直接存储数据,只需进行极少的转换。

追求速度用 LZ4,追求压缩率用 ZSTD

ClickHouse 在数据传输过程中支持多种压缩编解码器。常见的两个选项是:
  • LZ4:速度快且轻量。在几乎不增加 CPU 开销的情况下,它能显著减小数据量,因此非常适合高吞吐量插入,也是大多数 ClickHouse 客户端的默认选项。
  • ZSTD:压缩率更高,但 CPU 开销也更大。当网络传输成本较高时——例如跨区域或跨云提供商场景——它会很有用,不过会略微增加客户端侧算力消耗以及服务端解压时间。
最佳实践:除非带宽受限或会产生数据出口成本,否则请使用 LZ4;在这些情况下,可考虑 ZSTD。
FastFormats benchmark 的测试中,使用 LZ4 压缩的 Native 插入将数据量减少了 50% 以上,使 5.6 GiB 数据集的摄取时间从 150 秒缩短到 131 秒。切换到 ZSTD 后,同一数据集被压缩至 1.69 GiB,但服务端处理时间略有增加。

压缩可降低资源占用

压缩不仅能减少网络流量,还能提升服务器端的 CPU 和内存利用效率。使用压缩数据后,ClickHouse 接收的字节更少,解析大型输入所需的时间也更短。在可观测性等场景中,尤其是在多个并发客户端同时摄取数据时,这一点尤为重要。 对于 CPU 和内存的影响,LZ4 较小,ZSTD 则较为明显。即使在高负载下,由于数据量减少,服务器端效率仍会提升。 将压缩、批处理和高效的输入格式 (如 Native) 结合使用,可获得最佳摄取性能。 使用原生接口 (例如 clickhouse-client) 时,默认启用 LZ4 压缩。你也可以通过设置切换为 ZSTD。 使用 HTTP 接口 时,可通过 Content-Encoding 请求头启用压缩 (例如 Content-Encoding: lz4) 。整个载荷必须在发送前完成压缩。

成本较低时进行预排序

在插入前按主键对数据进行预排序,可以提高 ClickHouse 的摄取效率,尤其是在大批次场景下。 如果数据到达时已经排好序,ClickHouse 在创建分片时就可以跳过或简化内部排序步骤,从而降低 CPU 使用率并加快插入过程。预排序也有助于提升压缩效率,因为相似的值会集中在一起——这使得 LZ4 或 ZSTD 等编解码器能够获得更高的压缩率。与大批次插入和压缩结合使用时,这种做法尤其有利,因为它既能减少处理开销,也能减少传输的数据量。 也就是说,预排序是一项可选优化,而非必需。 ClickHouse 借助并行处理能非常高效地完成排序,在很多情况下,由服务端排序比在客户端预排序更快,或者更方便。 我们建议,只有在数据本身已基本有序,或者客户端资源 (CPU、内存) 充足且有富余时,才进行预排序。 对于延迟敏感或高吞吐的使用场景 (例如可观测性场景) ,如果数据是乱序到达的,或者来自大量 agent,通常更适合跳过预排序,直接利用 ClickHouse 的内置性能。

异步插入

当客户端侧批处理不可行时,ClickHouse 的异步插入提供了一种强有力的替代方案。这在可观测性工作负载中尤其有价值,因为成百上千个 agent 会持续发送数据——日志、指标、链路追踪——而且这些数据通常都是小型、实时的载荷。在这类环境中,在客户端侧缓冲数据会增加复杂性,因为需要集中式队列来确保能够发送足够大的批次。
不建议在同步模式下发送大量小批次,这会导致创建许多 parts,进而造成查询性能下降,并引发”too many part”错误。
异步插入会先将传入数据写入内存缓冲区,再根据可配置的阈值将其刷写到存储,从而把批处理责任从客户端转移到服务器端。这种方式可显著减少 parts 创建开销、降低 CPU 使用率,并确保摄取即使在高并发下也能保持高效。 其核心行为由 async_insert 设置控制。 异步插入同时支持 HTTP 和原生 TCP 接口。 启用后 (async_insert = 1) ,插入操作会先被缓冲,只有在满足以下某个刷写条件时才会写入磁盘: 哪个阈值先达到,就会触发刷写。 这一批处理过程对客户端是透明的,并帮助 ClickHouse 高效合并来自多个来源的插入流量。不过,在发生刷写之前,这些数据无法被查询。需要特别注意的是,针对每种插入形态与设置组合,都会有多个缓冲区;而在集群中,缓冲区则按节点分别维护——从而能够在多租户环境中实现细粒度控制。除此之外,插入机制与同步插入中描述的内容完全相同。

选择返回模式

可以通过 wait_for_async_insert 设置进一步细化异步插入的行为。 当该值设为 1 (默认值) 时,ClickHouse 只有在数据成功写入磁盘后才会确认插入。这样可以提供更强的持久性保障,也让错误处理更直接:如果在刷写期间发生问题,错误会返回给客户端。对于大多数生产场景,尤其是在必须可靠跟踪插入失败时,建议使用此模式。 基准测试 表明,得益于自适应插入以及稳定的 part 创建行为,它在并发场景下也能很好地扩展——无论是运行 200 个还是 500 个客户端。 wait_for_async_insert = 0 设为 0 会启用“发出即忘”模式。在这种模式下,server 会在数据进入缓冲区后立即确认插入,而不会等待其写入存储。 这种方式可实现超低延迟插入和最高吞吐量,非常适合高速度、低关键性的数据场景。但这也有代价:无法保证数据一定会持久化,错误只会在刷写期间暴露,而且失败的插入也没有 dead-letter queue——要追踪失败,只能事后检查服务器日志和系统表。只有在你的 workload 可以容忍数据丢失时,才应使用此模式。 基准测试还表明,当缓冲区刷写不频繁时 (例如每 30 秒一次) ,part 数量会显著减少,CPU 使用率也会更低,但静默失败的风险依然存在。 我们的强烈建议是:如果使用异步插入,请使用 async_insert=1,wait_for_async_insert=1。使用 wait_for_async_insert=0 风险很高,因为即使发生错误,你的 INSERT 客户端也可能毫不知情;此外,如果客户端在 ClickHouse server 需要降低写入速度并施加反压以确保服务可靠性时仍持续快速写入,还可能导致潜在过载。

自适应异步插入

从 24.2 版本开始,ClickHouse 默认使用自适应刷写超时 (async_insert_use_adaptive_busy_timeout) 。不再使用固定的刷写间隔,而是根据传入数据速率,在最小值 (async_insert_busy_timeout_min_ms,默认 50 毫秒) 和最大值 (async_insert_busy_timeout_max_ms,默认 200 毫秒,Cloud 上为 1000 毫秒) 之间动态调整超时。 当数据频繁到达时,超时会保持更接近最小值,以便更早刷写并降低端到端延迟。当数据较为稀疏时,超时则会增大并趋近最大值,以积累更大的批次。这在默认模式 (wait_for_async_insert=1) 下尤其有用,因为如果使用固定且较高的超时,即使数据已经可以刷写,客户端仍需阻塞等待整个时间间隔结束。

错误处理

Schema 验证和数据解析发生在缓冲区 刷写 时,而不是在收到插入时进行。如果某个插入查询中的任意一行出现解析错误或类型错误,该查询中的所有数据都不会被 刷写——整个查询的载荷都会被拒绝。在默认模式 (wait_for_async_insert=1) 下,错误会返回给客户端。在发出即忘模式下,错误会被写入服务器日志和 system.asynchronous_inserts 表。 每次 刷写 都会针对缓冲区中每个不同的分区键值至少创建一个 part。即使对于没有分区键的表,如果缓冲数据超过 max_insert_block_size (默认约 100 万行) ,单次 刷写 也可能生成多个 parts。
即使使用了异步插入,如果分区键的基数较高,你仍然可能遇到”parts 过多”错误。

去重与可靠性

默认情况下,ClickHouse 会对同步插入自动去重,因此在发生故障时进行重试也是安全的。不过,对异步插入而言,除非显式启用,否则该功能默认关闭 (如果你有依赖它的 materialized view,则不应启用——参见 issue) 。 在实际使用中,如果开启了去重,并且同一次插入因超时或网络中断等原因被重试,ClickHouse 就可以安全地忽略重复数据。这有助于保持幂等性,避免数据被重复写入。

启用异步插入

可以为特定用户或特定查询启用异步插入:
  • 在用户级别启用异步插入。此示例使用用户 default;如果你创建了其他用户,请将其替换为相应的用户名:
    ALTER USER default SETTINGS async_insert = 1
    
  • 你可以通过插入查询的 SETTINGS 子句指定异步插入设置:
    INSERT INTO YourTable SETTINGS async_insert=1, wait_for_async_insert=1 VALUES (...)
    
  • 使用 ClickHouse 编程语言客户端时,也可以将异步插入设置指定为连接参数。 例如,使用 ClickHouse Java JDBC 驱动连接到 ClickHouse Cloud 时,可以在 JDBC 连接字符串中这样设置:
    "jdbc:ch://HOST.clickhouse.cloud:8443/?user=default&password=PASSWORD&ssl=true&custom_http_params=async_insert=1,wait_for_async_insert=1"
    
异步插入不适用于 INSERT INTO ... SELECT 查询。当插入包含 SELECT 子句时,无论 async_insert 设置如何,查询始终都会同步执行。

关闭时刷新缓冲区

如需刷新所有待处理的 async insert 缓冲区 (例如在优雅关闭期间或维护前) ,请运行:
SYSTEM FLUSH ASYNC INSERT QUEUE
这可确保在服务器停止前,所有缓冲的数据都会先写入存储。

与缓冲表的比较

异步插入是 缓冲表 的现代替代方案。主要区别如下:
  • 无需修改 DDL。 异步插入对用户是透明的——你只需启用一个设置,无需创建额外的表。
  • 按形态缓冲。 异步插入会针对每种唯一的查询形态和设置组合维护单独的缓冲区,从而实现更细粒度的刷写策略。缓冲表则是每个目标表只有一个缓冲区。
  • 持久性。 在默认模式 (wait_for_async_insert=1) 下,客户端收到确认之前,数据就已写入磁盘。缓冲表的行为类似“发出即忘”——如果发生崩溃,缓冲中的数据会丢失。
  • 集群行为。 在集群中,异步插入缓冲区按节点分别维护。缓冲表则需要在每个节点上显式创建。

选择一种接口——HTTP 或原生协议

原生接口

ClickHouse 为数据摄取提供了两种主要接口:原生接口HTTP 接口——两者在性能与灵活性之间各有取舍。原生接口由 clickhouse-client 以及 Go、C++ 等部分语言的客户端使用,专为高性能而设计。它始终以 ClickHouse 高效的 Native 格式 传输数据,支持使用 LZ4 或 ZSTD 进行按块压缩,并通过将解析、格式转换等工作卸载到客户端,尽量减少服务端处理。 它甚至支持在客户端计算 MATERIALIZED 和 DEFAULT 列值,使服务端能够完全跳过这些步骤。因此,在效率至关重要的高吞吐量摄取场景中,原生接口是理想选择。

HTTP

与许多传统数据库不同,ClickHouse 也支持 HTTP 接口。**相比之下,它更强调兼容性和灵活性。**它支持以任何受支持的格式发送数据——包括 JSON、CSV、Parquet 等——并且已被大多数 ClickHouse 客户端广泛支持,包括 Python、Java、JavaScript 和 Rust。 与 ClickHouse 的原生协议相比,这种方式通常更合适,因为它便于通过负载均衡器轻松切换流量。我们预计原生协议在插入性能上会略有优势,因为它的额外开销更低一些。 不过,HTTP 接口缺少原生协议那样更深度的集成,也无法执行客户端侧优化,例如物化值的计算或自动转换为 Native 格式。虽然 HTTP 插入仍可通过标准 HTTP 请求头进行压缩 (例如 Content-Encoding: lz4) ,但压缩作用于整个载荷,而不是单独的数据块。因此,在更看重协议简单性、负载均衡或广泛格式兼容性,而非极致性能的环境中,通常会优先选择这种接口。 有关这些接口的更详细说明,请参见此处
最后修改于 2026年6月10日