> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-fix-nav-issues.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 选择插入策略

> 介绍如何在 ClickHouse 中选择插入策略

export const Image = ({img, alt, size}) => {
  return <Frame>
      <img src={img} alt={alt} />
    </Frame>;
};

高效的数据摄取是构建高性能 ClickHouse 部署的基础。选择合适的插入策略会显著影响吞吐量、成本和可靠性。本节将概述最佳实践、利弊权衡以及配置选项，帮助你根据自身工作负载做出正确决策。

<Note>
  以下内容假设你是通过客户端将数据推送到 ClickHouse。如果你是将数据拉取到 ClickHouse，例如使用内置表函数 [s3](/zh/reference/functions/table-functions/s3) 和 [gcs](/zh/reference/functions/table-functions/gcs)，我们建议参考指南 ["优化 S3 插入和读取性能"](/zh/integrations/connectors/data-ingestion/AWS/performance)。
</Note>

<div id="synchronous-inserts-by-default">
  ## 默认使用同步插入
</div>

默认情况下，向 ClickHouse 执行的插入都是同步的。每个插入查询都会立即在磁盘上创建一个存储数据分片，其中包括元数据和索引。

<Info>
  **如果你可以在客户端将数据按批次处理，请使用同步插入**

  如果不能，请参阅下方的[异步插入](#asynchronous-inserts)。
</Info>

下面我们简要介绍 ClickHouse 的 MergeTree 插入机制：

<Image img="https://mintcdn.com/private-7c7dfe99-fix-nav-issues/Y9kcWM6RbYppspJn/images/bestpractices/insert_process.png?fit=max&auto=format&n=Y9kcWM6RbYppspJn&q=85&s=b4453cffd4767b5c9c238a90c70dfeae" size="lg" alt="插入过程" background="black" width="1837" height="1297" data-path="images/bestpractices/insert_process.png" />

<div id="client-side-steps">
  #### 客户端侧步骤
</div>

为获得最佳性能，数据必须①[按批次处理](https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse#data-needs-to-be-batched-for-optimal-performance)，因此批次大小就成了**首要决策**。

ClickHouse 会将插入的数据存储在磁盘上，并[按](/zh/guides/clickhouse/data-modelling/sparse-primary-indexes#data-is-stored-on-disk-ordered-by-primary-key-columns)表的主键列排序。因此，**第二个决策**是是否要在传输到服务器之前②先对数据进行预排序。如果某个批次到达时已经按主键列预先排好序，ClickHouse 就可以[跳过](https://github.com/ClickHouse/ClickHouse/blob/94ce8e95404e991521a5608cd9d636ff7269743d/src/Storages/MergeTree/MergeTreeDataWriter.cpp#L595)⑩排序步骤，从而加快摄取速度。

如果待摄取的数据没有预定义格式，**关键决策**就是选择哪种格式。ClickHouse 支持以[70 多种格式](/zh/reference/formats)插入数据。不过，在使用 ClickHouse 命令行客户端或编程语言客户端时，这一选择通常会自动完成。如有需要，也可以显式覆盖这一自动选择。

下一个**主要决策**是④是否在传输到 ClickHouse 服务器之前先压缩数据。压缩可以减小传输体积并提高网络效率，从而加快数据传输速度并降低带宽占用，尤其是在处理大型数据集时。

数据会⑤通过 ClickHouse 网络接口传输——即 [native](/zh/concepts/features/interfaces/tcp) 或[ HTTP](/zh/concepts/features/interfaces/http) 接口 (我们将在本文后面对它们进行[比较](https://clickhouse.com/blog/clickhouse-input-format-matchup-which-is-fastest-most-efficient#clickhouse-client-defaults)) 。

<div id="server-side-steps">
  #### 服务器端步骤
</div>

在 ⑥ 收到数据后，如果使用了压缩，ClickHouse 会在 ⑦ 先将其解压，然后在 ⑧ 按原始发送格式进行解析。

ClickHouse 会根据该格式化数据中的值以及目标表的 [DDL](/zh/reference/statements/create/table) 语句，在内存中以 MergeTree 格式 ⑨ 构建一个 [块](/zh/resources/develop-contribute/introduction/architecture#block)；如果这些行尚未预排序，则在 ⑩ 按主键列对其[排序](/zh/concepts/core-concepts/parts#what-are-table-parts-in-clickhouse)；在 ⑪ 创建一个[稀疏主索引](/zh/guides/clickhouse/data-modelling/sparse-primary-indexes)；在 ⑫ 应用[按列压缩](/zh/concepts/core-concepts/parts#what-are-table-parts-in-clickhouse)；并在 ⑬ 将数据作为新的 ⑭ [数据分片](/zh/concepts/core-concepts/parts) 写入磁盘。

<div id="batch-inserts-if-synchronous">
  ### 同步时使用批次插入
</div>

上述机制表明，无论插入大小如何，都会产生固定开销，因此批次大小是优化摄取吞吐量最重要的因素。对插入进行批处理可以降低这部分开销在总插入耗时中的占比，并提升处理效率。

我们建议每批至少插入 1,000 行数据，理想情况下每批应为 10,000–100,000 行。更少但更大的插入可减少写入的 parts 数量，减轻 merge 负载，并降低整体系统资源消耗。

**要让同步插入策略发挥效果，必须在客户端进行这种批处理。**

如果你无法在客户端对数据进行批处理，ClickHouse 支持异步插入，可将批处理转移到服务器端 ([参见异步插入](/zh/concepts/best-practices/selecting-an-insert-strategy#asynchronous-inserts)) 。

<Tip>
  无论插入大小如何，我们都建议将插入查询的频率控制在大约每秒一次。原因在于，新创建的 parts 会在后台合并成更大的 parts (以便针对读查询优化数据) ；如果每秒发送过多插入查询，可能会导致后台合并跟不上新 parts 的生成速度。不过，使用异步插入时，你可以采用更高的每秒插入查询频率 (参见[异步插入](/zh/concepts/best-practices/selecting-an-insert-strategy#asynchronous-inserts)) 。
</Tip>

<div id="ensure-idempotent-retries">
  ### 确保重试具备幂等性
</div>

同步插入同样具有**幂等性**。使用 MergeTree 引擎时，ClickHouse 默认会对插入进行去重。这可以避免以下这类结果不确定的故障情况：

* 插入已成功，但由于网络中断，客户端始终未收到确认。
* 插入在服务端失败并超时。

在这两种情况下，**重试插入**都是安全的——前提是批次内容和顺序保持完全一致。因此，客户端必须以一致的方式重试，不能修改或重排数据。

<div id="choose-the-right-insert-target">
  ### 选择合适的插入目标
</div>

对于分片集群，你有两个选择：

* 直接插入 **MergeTree** 或 **ReplicatedMergeTree** 表。如果客户端能够跨分片进行负载均衡，这是效率最高的方案。设置 `internal_replication = true` 后，ClickHouse 会透明地处理复制。
* 插入到 [分布式表](/zh/reference/engines/table-engines/special/distributed)。这样客户端可以将数据发送到任意节点，再由 ClickHouse 将其转发到正确的分片。这种方式更简单，但由于多了一步转发，性能会略低一些。仍建议使用 `internal_replication = true`。

**在 ClickHouse Cloud 中，所有节点都会读写同一个分片。插入会自动在各节点之间均衡。你只需将插入请求发送到暴露的端点即可。**

<div id="choose-the-right-format">
  ### 选择合适的格式
</div>

选择合适的输入格式，对于在 ClickHouse 中高效摄取数据至关重要。ClickHouse 支持 70 多种格式，选择性能最佳的方案会显著影响插入速度、CPU 和内存使用情况，以及整体系统效率。

虽然灵活性对数据工程和基于文件的导入很有帮助，但**应用程序应优先选择面向性能的格式**：

* **Native 格式** (推荐) ：效率最高。采用列式格式，服务端几乎无需解析。Go 和 Python 客户端默认使用该格式。
* **RowBinary**：高效的行式格式；如果客户端侧难以完成列式转换，它是理想选择。Java 客户端使用该格式。
* **JSONEachRow**：易于使用，但解析开销较高。适合低数据量场景或快速集成。

<div id="use-compression">
  ### 使用压缩
</div>

压缩在降低网络开销、加快插入速度以及减少 ClickHouse 存储成本方面至关重要。合理使用压缩，无需更改数据格式或 schema，即可提升摄取性能。

对插入数据进行压缩可以减小通过网络传输的载荷大小，从而减少带宽占用并加快传输速度。

对于插入操作，压缩在与 Native 格式 搭配使用时尤其有效，因为这种格式本身就与 ClickHouse 内部的列式存储模型相匹配。在这种情况下，服务器可以高效解压并直接存储数据，只需进行极少的转换。

<div id="use-lz4-for-speed-zstd-for-compression-ratio">
  #### 追求速度用 LZ4，追求压缩率用 ZSTD
</div>

ClickHouse 在数据传输过程中支持多种压缩编解码器。常见的两个选项是：

* **LZ4**：速度快且轻量。在几乎不增加 CPU 开销的情况下，它能显著减小数据量，因此非常适合高吞吐量插入，也是大多数 ClickHouse 客户端的默认选项。
* **ZSTD**：压缩率更高，但 CPU 开销也更大。当网络传输成本较高时——例如跨区域或跨云提供商场景——它会很有用，不过会略微增加客户端侧算力消耗以及服务端解压时间。

最佳实践：除非带宽受限或会产生数据出口成本，否则请使用 LZ4；在这些情况下，可考虑 ZSTD。

<Note>
  在 [FastFormats benchmark](https://clickhouse.com/blog/clickhouse-input-format-matchup-which-is-fastest-most-efficient) 的测试中，使用 LZ4 压缩的 Native 插入将数据量减少了 50% 以上，使 5.6 GiB 数据集的摄取时间从 150 秒缩短到 131 秒。切换到 ZSTD 后，同一数据集被压缩至 1.69 GiB，但服务端处理时间略有增加。
</Note>

<div id="compression-reduces-resource-usage">
  #### 压缩可降低资源占用
</div>

压缩不仅能减少网络流量，还能提升服务器端的 CPU 和内存利用效率。使用压缩数据后，ClickHouse 接收的字节更少，解析大型输入所需的时间也更短。在可观测性等场景中，尤其是在多个并发客户端同时摄取数据时，这一点尤为重要。

对于 CPU 和内存的影响，LZ4 较小，ZSTD 则较为明显。即使在高负载下，由于数据量减少，服务器端效率仍会提升。

**将压缩、批处理和高效的输入格式 (如 Native) 结合使用，可获得最佳摄取性能。**

使用原生接口 (例如 [clickhouse-client](/zh/concepts/features/interfaces/cli)) 时，默认启用 LZ4 压缩。你也可以通过设置切换为 ZSTD。

使用 [HTTP 接口](/zh/concepts/features/interfaces/http) 时，可通过 Content-Encoding 请求头启用压缩 (例如 Content-Encoding: lz4) 。整个载荷必须在发送前完成压缩。

<div id="pre-sort-if-low-cost">
  ### 成本较低时进行预排序
</div>

在插入前按主键对数据进行预排序，可以提高 ClickHouse 的摄取效率，尤其是在大批次场景下。

如果数据到达时已经排好序，ClickHouse 在创建分片时就可以跳过或简化内部排序步骤，从而降低 CPU 使用率并加快插入过程。预排序也有助于提升压缩效率，因为相似的值会集中在一起——这使得 LZ4 或 ZSTD 等编解码器能够获得更高的压缩率。与大批次插入和压缩结合使用时，这种做法尤其有利，因为它既能减少处理开销，也能减少传输的数据量。

**也就是说，预排序是一项可选优化，而非必需。** ClickHouse 借助并行处理能非常高效地完成排序，在很多情况下，由服务端排序比在客户端预排序更快，或者更方便。

**我们建议，只有在数据本身已基本有序，或者客户端资源 (CPU、内存) 充足且有富余时，才进行预排序。** 对于延迟敏感或高吞吐的使用场景 (例如可观测性场景) ，如果数据是乱序到达的，或者来自大量 agent，通常更适合跳过预排序，直接利用 ClickHouse 的内置性能。

<div id="asynchronous-inserts">
  ## 异步插入
</div>

当客户端侧批处理不可行时，ClickHouse 的异步插入提供了一种强有力的替代方案。这在可观测性工作负载中尤其有价值，因为成百上千个 agent 会持续发送数据——日志、指标、链路追踪——而且这些数据通常都是小型、实时的载荷。在这类环境中，在客户端侧缓冲数据会增加复杂性，因为需要集中式队列来确保能够发送足够大的批次。

<Note>
  不建议在同步模式下发送大量小批次，这会导致创建许多 parts，进而造成查询性能下降，并引发["too many part"](/zh/resources/support-center/knowledge-base/troubleshooting/exception-too-many-parts)错误。
</Note>

异步插入会先将传入数据写入内存缓冲区，再根据可配置的阈值将其刷写到存储，从而把批处理责任从客户端转移到服务器端。这种方式可显著减少 parts 创建开销、降低 CPU 使用率，并确保摄取即使在高并发下也能保持高效。

其核心行为由 [`async_insert`](/zh/reference/settings/session-settings#async_insert) 设置控制。

<Image img="https://mintcdn.com/private-7c7dfe99-fix-nav-issues/Y9kcWM6RbYppspJn/images/bestpractices/async_inserts.png?fit=max&auto=format&n=Y9kcWM6RbYppspJn&q=85&s=2f7f3d55ee3b2eacde2fca9bd1796e3c" size="lg" alt="异步插入" width="1600" height="1130" data-path="images/bestpractices/async_inserts.png" />

异步插入同时支持 HTTP 和原生 TCP 接口。

启用后 (`async_insert = 1`) ，插入操作会先被缓冲，只有在满足以下某个刷写条件时才会写入磁盘：

* 缓冲区达到指定的数据大小 ([`async_insert_max_data_size`](/zh/reference/settings/session-settings#async_insert_max_data_size)，默认 100 MiB) 。
* 达到时间阈值 ([`async_insert_busy_timeout_ms`](/zh/reference/settings/session-settings#async_insert_busy_timeout_max_ms)，默认 200 ms，在 Cloud 上为 1000 ms) 。
* 累积到最大插入查询数 ([`async_insert_max_query_number`](/zh/reference/settings/session-settings#async_insert_max_query_number)，默认 450) 。

哪个阈值先达到，就会触发刷写。

这一批处理过程对客户端是透明的，并帮助 ClickHouse 高效合并来自多个来源的插入流量。不过，在发生刷写之前，这些数据无法被查询。需要特别注意的是，针对每种插入形态与设置组合，都会有多个缓冲区；而在集群中，缓冲区则按节点分别维护——从而能够在多租户环境中实现细粒度控制。除此之外，插入机制与[同步插入](/zh/concepts/best-practices/selecting-an-insert-strategy#synchronous-inserts-by-default)中描述的内容完全相同。

<div id="choosing-a-return-mode">
  ### 选择返回模式
</div>

可以通过 [`wait_for_async_insert`](/zh/reference/settings/session-settings#wait_for_async_insert) 设置进一步细化异步插入的行为。

当该值设为 1 (默认值) 时，ClickHouse 只有在数据成功写入磁盘后才会确认插入。这样可以提供更强的持久性保障，也让错误处理更直接：如果在刷写期间发生问题，错误会返回给客户端。对于大多数生产场景，尤其是在必须可靠跟踪插入失败时，建议使用此模式。

[基准测试](https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse) 表明，得益于自适应插入以及稳定的 part 创建行为，它在并发场景下也能很好地扩展——无论是运行 200 个还是 500 个客户端。

将 `wait_for_async_insert = 0` 设为 0 会启用“发出即忘”模式。在这种模式下，server 会在数据进入缓冲区后立即确认插入，而不会等待其写入存储。

这种方式可实现超低延迟插入和最高吞吐量，非常适合高速度、低关键性的数据场景。但这也有代价：无法保证数据一定会持久化，错误只会在刷写期间暴露，而且失败的插入也没有 dead-letter queue——要追踪失败，只能事后检查服务器日志和系统表。只有在你的 workload 可以容忍数据丢失时，才应使用此模式。

[基准测试还表明](https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse)，当缓冲区刷写不频繁时 (例如每 30 秒一次) ，part 数量会显著减少，CPU 使用率也会更低，但静默失败的风险依然存在。

我们的强烈建议是：如果使用异步插入，请使用 `async_insert=1,wait_for_async_insert=1`。使用 `wait_for_async_insert=0` 风险很高，因为即使发生错误，你的 INSERT 客户端也可能毫不知情；此外，如果客户端在 ClickHouse server 需要降低写入速度并施加反压以确保服务可靠性时仍持续快速写入，还可能导致潜在过载。

<div id="adaptive-async-inserts">
  ### 自适应异步插入
</div>

从 24.2 版本开始，ClickHouse 默认使用自适应刷写超时 ([`async_insert_use_adaptive_busy_timeout`](/zh/reference/settings/session-settings#async_insert_use_adaptive_busy_timeout)) 。不再使用固定的刷写间隔，而是根据传入数据速率，在最小值 ([`async_insert_busy_timeout_min_ms`](/zh/reference/settings/session-settings#async_insert_busy_timeout_min_ms)，默认 50 毫秒) 和最大值 ([`async_insert_busy_timeout_max_ms`](/zh/reference/settings/session-settings#async_insert_busy_timeout_max_ms)，默认 200 毫秒，Cloud 上为 1000 毫秒) 之间动态调整超时。

当数据频繁到达时，超时会保持更接近最小值，以便更早刷写并降低端到端延迟。当数据较为稀疏时，超时则会增大并趋近最大值，以积累更大的批次。这在默认模式 (`wait_for_async_insert=1`) 下尤其有用，因为如果使用固定且较高的超时，即使数据已经可以刷写，客户端仍需阻塞等待整个时间间隔结束。

<div id="error-handling">
  ### 错误处理
</div>

Schema 验证和数据解析发生在缓冲区 刷写 时，而不是在收到插入时进行。如果某个插入查询中的任意一行出现解析错误或类型错误，**该查询中的所有数据都不会被 刷写**——整个查询的载荷都会被拒绝。在默认模式 (`wait_for_async_insert=1`) 下，错误会返回给客户端。在发出即忘模式下，错误会被写入服务器日志和 [`system.asynchronous_inserts`](/zh/reference/system-tables/asynchronous_inserts) 表。

每次 刷写 都会针对缓冲区中每个不同的分区键值至少创建一个 part。即使对于没有分区键的表，如果缓冲数据超过 [`max_insert_block_size`](/zh/reference/settings/session-settings#max_insert_block_size) (默认约 100 万行) ，单次 刷写 也可能生成多个 parts。

<Note>
  即使使用了异步插入，如果分区键的基数较高，你仍然可能遇到["parts 过多"](/zh/resources/support-center/knowledge-base/troubleshooting/exception-too-many-parts)错误。
</Note>

<div id="deduplication-and-reliability">
  ### 去重与可靠性
</div>

默认情况下，ClickHouse 会对同步插入自动去重，因此在发生故障时进行重试也是安全的。不过，对异步插入而言，除非显式启用，否则该功能默认关闭 (如果你有依赖它的 materialized view，则不应启用——[参见 issue](https://github.com/ClickHouse/ClickHouse/issues/66003)) 。

在实际使用中，如果开启了去重，并且同一次插入因超时或网络中断等原因被重试，ClickHouse 就可以安全地忽略重复数据。这有助于保持幂等性，避免数据被重复写入。

<div id="enabling-asynchronous-inserts">
  ### 启用异步插入
</div>

可以为特定用户或特定查询启用异步插入：

* 在用户级别启用异步插入。此示例使用用户 `default`；如果你创建了其他用户，请将其替换为相应的用户名：
  ```sql theme={null}
  ALTER USER default SETTINGS async_insert = 1
  ```
* 你可以通过插入查询的 SETTINGS 子句指定异步插入设置：
  ```sql theme={null}
  INSERT INTO YourTable SETTINGS async_insert=1, wait_for_async_insert=1 VALUES (...)
  ```
* 使用 ClickHouse 编程语言客户端时，也可以将异步插入设置指定为连接参数。

  例如，使用 ClickHouse Java JDBC 驱动连接到 ClickHouse Cloud 时，可以在 JDBC 连接字符串中这样设置：

  ```bash theme={null}
  "jdbc:ch://HOST.clickhouse.cloud:8443/?user=default&password=PASSWORD&ssl=true&custom_http_params=async_insert=1,wait_for_async_insert=1"
  ```

<Note>
  异步插入不适用于 `INSERT INTO ... SELECT` 查询。当插入包含 `SELECT` 子句时，无论 `async_insert` 设置如何，查询始终都会同步执行。
</Note>

<div id="flushing-buffers-on-shutdown">
  ### 关闭时刷新缓冲区
</div>

如需刷新所有待处理的 async insert 缓冲区 (例如在优雅关闭期间或维护前) ，请运行：

```sql theme={null}
SYSTEM FLUSH ASYNC INSERT QUEUE
```

这可确保在服务器停止前，所有缓冲的数据都会先写入存储。

<div id="comparison-with-buffer-tables">
  ### 与缓冲表的比较
</div>

异步插入是 [缓冲表](/zh/reference/engines/table-engines/special/buffer) 的现代替代方案。主要区别如下：

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

<div id="choose-an-interface">
  ## 选择一种接口——HTTP 或原生协议
</div>

<div id="choose-an-interface-native">
  ### 原生接口
</div>

ClickHouse 为数据摄取提供了两种主要接口：**原生接口**和**HTTP 接口**——两者在性能与灵活性之间各有取舍。原生接口由 [clickhouse-client](/zh/concepts/features/interfaces/cli) 以及 Go、C++ 等部分语言的客户端使用，专为高性能而设计。它始终以 ClickHouse 高效的 Native 格式 传输数据，支持使用 LZ4 或 ZSTD 进行按块压缩，并通过将解析、格式转换等工作卸载到客户端，尽量减少服务端处理。

它甚至支持在客户端计算 MATERIALIZED 和 DEFAULT 列值，使服务端能够完全跳过这些步骤。因此，在效率至关重要的高吞吐量摄取场景中，原生接口是理想选择。

<div id="choose-an-interface-http">
  ### HTTP
</div>

与许多传统数据库不同，ClickHouse 也支持 HTTP 接口。\*\*相比之下，它更强调兼容性和灵活性。\*\*它支持以[任何受支持的格式](/zh/guides/clickhouse/data-formats/intro)发送数据——包括 JSON、CSV、Parquet 等——并且已被大多数 ClickHouse 客户端广泛支持，包括 Python、Java、JavaScript 和 Rust。

与 ClickHouse 的原生协议相比，这种方式通常更合适，因为它便于通过负载均衡器轻松切换流量。我们预计原生协议在插入性能上会略有优势，因为它的额外开销更低一些。

不过，HTTP 接口缺少原生协议那样更深度的集成，也无法执行客户端侧优化，例如物化值的计算或自动转换为 Native 格式。虽然 HTTP 插入仍可通过标准 HTTP 请求头进行压缩 (例如 `Content-Encoding: lz4`) ，但压缩作用于整个载荷，而不是单独的数据块。因此，在更看重协议简单性、负载均衡或广泛格式兼容性，而非极致性能的环境中，通常会优先选择这种接口。

有关这些接口的更详细说明，请参见[此处](/zh/concepts/features/interfaces/overview)。
