跳到主要内容
跳到主要内容

选择插入策略

高效的数据摄取是高性能 ClickHouse 部署的基础。选择合适的插入策略可以显著影响吞吐量、成本和可靠性。本节概述了最佳实践、权衡取舍和配置选项,帮助您为工作负载做出正确的决策。

注意

以下内容假设您通过客户端将数据推送到 ClickHouse。如果您是将数据拉取到 ClickHouse,例如使用内置表函数如 s3gcs,我们推荐参考指南 "优化 S3 插入和读取性能"

默认情况下为同步写入

默认情况下,对 ClickHouse 的写入是同步的。每个 insert 查询都会立即在磁盘上创建一个存储分片(part),包括元数据和索引。

如果可以在客户端进行批处理,请使用同步写入

如果不行,请参见下文的 异步写入

我们在下面简要回顾 ClickHouse 的 MergeTree 写入机制:

写入流程

客户端侧步骤

为了获得最佳性能,数据必须进行 ①批处理,这使得批大小成为首要决策

ClickHouse 将写入的数据存储在磁盘上,并按照表的主键列排序后存储第二个决策是是否在发送到服务器之前,对数据进行 ② 预排序。如果一个批次在到达时已经按主键列预排序,ClickHouse 就可以跳过第 ⑩ 步排序,从而加速数据摄取。

如果要摄取的数据没有预定义格式,那么关键决策是选择一种格式。ClickHouse 支持以超过 70 种格式写入数据。不过,当使用 ClickHouse 命令行客户端或编程语言客户端时,这个选择通常会自动完成。如果需要,也可以显式覆盖这种自动选择。

下一个重要决策是 ④ 是否在将数据传输到 ClickHouse 服务器之前对其进行压缩。压缩可以减少传输大小并提升网络效率,从而加快数据传输并降低带宽占用,尤其对大规模数据集尤为有效。

数据会被 ⑤ 传输到 ClickHouse 的某个网络接口——原生接口或 HTTP 接口(我们会在本文后面对其进行比较)。

服务器侧步骤

在 ⑥ 接收数据之后,如果使用了压缩,ClickHouse 会先对其进行 ⑦ 解压,然后从原始发送格式中进行 ⑧ 解析。

使用该格式化数据中的值以及目标表的 DDL 语句,ClickHouse 会以 MergeTree 格式构建一个内存中的 ⑨块(block),在数据未预先排序的情况下,按主键列对行进行 ⑩排序,创建 ⑪稀疏主索引,对每一列应用 ⑫按列压缩,并将数据作为一个新的 ⑭数据分片(data part)写入磁盘。

同步写入时请进行批量插入

The above mechanics illustrate a constant overhead regardless of the insert size, making batch size the single most important optimization for ingest throughput. Batching inserts reduce the overhead as a proportion of total insert time and improves processing efficiency.

We recommend inserting data in batches of at least 1,000 rows, and ideally between 10,000–100,000 rows. Fewer, larger inserts reduce the number of parts written, minimize merge load, and lower overall system resource usage.

For a synchronous insert strategy to be effective this client-side batching is required.

If you're unable to batch data client-side, ClickHouse supports asynchronous inserts that shift batching to the server (see).

提示

Regardless of the size of your inserts, we recommend keeping the number of insert queries around one insert query per second. The reason for this recommendation is that the created parts are merged to larger parts in the background (in order to optimize your data for read queries), and sending too many insert queries per second can lead to situations where the background merging can't keep up with the number of new parts. However, you can use a higher rate of insert queries per second when you use asynchronous inserts (see asynchronous inserts).

确保幂等重试

同步写入同样是幂等的。在使用 MergeTree 引擎时,ClickHouse 默认会对写入进行去重。这可以防止以下不明确的失败场景:

  • 写入已经成功,但由于网络中断,客户端从未收到确认。
  • 写入在服务器端失败并发生超时。

在这两种情况下,只要批次的内容和顺序保持完全一致,重试写入就是安全的。基于此原因,客户端在重试时必须保持一致,不得修改或重新排序数据。

选择合适的写入目标

对于分片集群,有两种选择:

  • 直接写入 MergeTreeReplicatedMergeTree 表。当客户端可以在分片之间执行负载均衡时,这是最高效的选项。设置 internal_replication = true 时,ClickHouse 会透明地处理复制。
  • 写入一个 Distributed 表。这样客户端可以将数据发送到任意节点,由 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 基准测试 中,使用 LZ4 压缩的 Native 插入将数据大小减少了 50% 以上,在 5.6 GiB 数据集上将摄取时间从 150 秒缩短至 131 秒。切换到 ZSTD 后,同一数据集被压缩到 1.69 GiB,但服务端处理时间略有增加。

压缩可降低资源使用

压缩不仅减少网络流量——还提升了服务端的 CPU 与内存效率。对于压缩数据,ClickHouse 接收的字节更少,并且在解析大体量输入时花费的时间更短。当从多个并发客户端(例如在可观测性场景中)进行摄取时,这一优势尤为重要。

对于 LZ4,压缩对 CPU 和内存的影响较小;对于 ZSTD,则影响适中。即使在高负载下,由于数据量减少,服务端效率仍然会得到提升。

将压缩与批量插入以及高效输入格式(如 Native)结合使用,可获得最佳摄取性能。

在使用原生接口(例如 clickhouse-client)时,会默认启用 LZ4 压缩。你也可以通过设置切换为 ZSTD。

使用 HTTP 接口 时,请通过 Content-Encoding 头部应用压缩(例如 Content-Encoding: lz4)。整个有效载荷必须在发送前完成压缩。

如果代价低则预排序

在插入前按主键对数据进行预排序,可以提升 ClickHouse 中的摄取效率,尤其是对于大批量插入。

当数据以预排序形式到达时,ClickHouse 在创建数据 part 的过程中可以跳过或简化内部排序步骤,从而降低 CPU 使用并加快插入过程。预排序还会提升压缩效率,因为相似值被聚集在一起——使 LZ4 或 ZSTD 等编解码器可以获得更好的压缩率。在与大批量插入和压缩结合使用时,这种方式尤为有益,因为它同时减少了处理开销和传输数据量。

不过,预排序是一种可选优化——不是必需条件。 ClickHouse 通过并行处理对数据进行高效排序,在许多情况下,服务端排序比在客户端进行预排序更快或更方便。

我们仅在数据本身已经接近有序,或客户端侧资源(CPU、内存)充足且有富余时,才建议进行预排序。 在对延迟敏感或高吞吐量的场景(例如可观测性)中,数据往往是乱序到达或来自大量 Agent,此时通常更好的做法是跳过预排序,直接依赖 ClickHouse 的内置性能。

异步插入

Asynchronous inserts in ClickHouse provide a powerful alternative when client-side batching isn't feasible. This is especially valuable in observability workloads, where hundreds or thousands of agents send data continuously—logs, metrics, traces—often in small, real-time payloads. Buffering data client-side in these environments increases complexity, requiring a centralized queue to ensure sufficiently large batches can be sent.

注意

Sending many small batches in synchronous mode is not recommended, leading to many parts being created. This will lead to poor query performance and "too many part" errors.

Asynchronous inserts shift batching responsibility from the client to the server by writing incoming data to an in-memory buffer, then flushing it to storage based on configurable thresholds. This approach significantly reduces part creation overhead, lowers CPU usage, and ensures ingestion remains efficient—even under high concurrency.

The core behavior is controlled via the async_insert setting.

Async inserts

When enabled (1), inserts are buffered and only written to disk once one of the flush conditions is met:

(1) the buffer reaches a specified size (async_insert_max_data_size) (2) a time threshold elapses (async_insert_busy_timeout_ms) or (3) a maximum number of insert queries accumulate (async_insert_max_query_number).

This batching process is invisible to clients and helps ClickHouse efficiently merge insert traffic from multiple sources. However, until a flush occurs, the data cannot be queried. Importantly, there are multiple buffers per insert shape and settings combination, and in clusters, buffers are maintained per node—enabling fine-grained control across multi-tenant environments. Insert mechanics are otherwise identical to those described for synchronous inserts.

Choosing a return mode

The behavior of asynchronous inserts is further refined using the wait_for_async_insert setting.

When set to 1 (the default), ClickHouse only acknowledges the insert after the data is successfully flushed to disk. This ensures strong durability guarantees and makes error handling straightforward: if something goes wrong during the flush, the error is returned to the client. This mode is recommended for most production scenarios, especially when insert failures must be tracked reliably.

Benchmarks show it scales well with concurrency—whether you're running 200 or 500 clients—thanks to adaptive inserts and stable part creation behavior.

Setting wait_for_async_insert = 0 enables "fire-and-forget" mode. Here, the server acknowledges the insert as soon as the data is buffered, without waiting for it to reach storage.

This offers ultra-low-latency inserts and maximal throughput, ideal for high-velocity, low-criticality data. However, this comes with trade-offs: there's no guarantee the data will be persisted, errors may only surface during flush, and it's difficult to trace failed inserts. Use this mode only if your workload can tolerate data loss.

Benchmarks also demonstrate substantial part reduction and lower CPU usage when buffer flushes are infrequent (e.g. every 30 seconds), but the risk of silent failure remains.

Our strong recommendation is to use async_insert=1,wait_for_async_insert=1 if using asynchronous inserts. Using wait_for_async_insert=0 is very risky because your INSERT client may not be aware if there are errors, and also can cause potential overload if your client continues to write quickly in a situation where the ClickHouse server needs to slow down the writes and create some backpressure in order to ensure reliability of the service.

Deduplication and reliability

By default, ClickHouse performs automatic deduplication for synchronous inserts, which makes retries safe in failure scenarios. However, this is disabled for asynchronous inserts unless explicitly enabled (this should not be enabled if you have dependent materialized views—see issue).

In practice, if deduplication is turned on and the same insert is retried—due to, for instance, a timeout or network drop—ClickHouse can safely ignore the duplicate. This helps maintain idempotency and avoids double-writing data. Still, it's worth noting that insert validation and schema parsing happen only during buffer flush—so errors (like type mismatches) will only surface at that point.

Enabling asynchronous inserts

Asynchronous inserts can be enabled for a particular user, or for a specific query:

  • Enabling asynchronous inserts at the user level. This example uses the user default, if you create a different user then substitute that username:

    ALTER USER default SETTINGS async_insert = 1
    
  • You can specify the asynchronous insert settings by using the SETTINGS clause of insert queries:

    INSERT INTO YourTable SETTINGS async_insert=1, wait_for_async_insert=1 VALUES (...)
    
  • You can also specify asynchronous insert settings as connection parameters when using a ClickHouse programming language client.

    As an example, this is how you can do that within a JDBC connection string when you use the ClickHouse Java JDBC driver for connecting to ClickHouse Cloud:

    "jdbc:ch://HOST.clickhouse.cloud:8443/?user=default&password=PASSWORD&ssl=true&custom_http_params=async_insert=1,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 原生协议,因为它允许通过负载均衡器轻松切换流量。与原生协议相比,我们预计插入性能会有细微差异,原生协议的开销会略小一些。

然而,它缺少原生协议的深度集成能力,无法执行诸如物化值计算或自动转换为 Native 格式等客户端优化。虽然通过 HTTP 进行插入时仍可使用标准 HTTP 头进行压缩(例如 Content-Encoding: lz4),但压缩是针对整个负载而非单个数据块进行的。在那些更看重协议简单性、负载均衡或广泛格式兼容性而非极致性能的环境中,通常会优先选择该接口。

有关这些接口的更详细说明,请参见此处