应选用 HTTP POST + Native 格式二进制流而非官方 C++ client,因其支持高并发、低开销批量写入,配合 parallel parsing、native date types 等参数可达成百万级吞吐。

ClickHouse 客户端选型:为什么别用官方 C++ client
官方 clickhouse-cpp 库不支持异步批量写入,且默认单连接、无连接池、序列化开销大,实测在高并发写入场景下吞吐卡在 5–10 万行/秒,远达不到“每秒数百万行”的要求。真正能扛住压力的是基于 HTTP 接口的轻量封装,或直接复用 libcurl + 手动拼接格式。
推荐路径:HTTP POST + Native 格式二进制流(非 JSON/TSV),这是 ClickHouse 官方最优化的写入通道。所有高性能客户端(如 clickhouse-cpp 的 BlockOutputStream 模式、Python 的 clickhouse-driver)底层都走这条路。
- 必须启用
input_format_parallel_parsing=1和output_format_native_date_types=1,否则日期类型解析慢 3–5 倍 - 禁用
send_logs_level(设为none),避免日志拖慢请求响应 - 不要用
INSERT INTO ... VALUES (...),(...)这种 SQL 字符串拼接方式——单次最多撑不过 1 万行,且 SQL 解析开销爆炸
Native 格式二进制协议怎么手写(关键字段+内存布局)
ClickHouse 的 Native 格式不是文档化的“协议”,而是内存镜像直传:先发表结构(TableStructure),再发数据块(Block),每个 Block 包含列名、类型、压缩标志、数据长度和原始字节。绕过它等于放弃性能。
实操要点:
立即学习“C++免费学习笔记(深入)”;
- 列定义必须严格匹配建表 DDL,包括
DateTime64(3, 'UTC')的精度和时区,错一个字节就报Code: 407. DB::Exception: Unknown data type - 字符串列要先写
UInt64长度,再写char[]内容;变长数组同理(先UInt64元素数,再逐个写元素) - 整数一律小端序;浮点用 IEEE754;
Nullable(T)列需额外一列UInt8bitmap 表示 null 标志 - 用
snappy或lz4压缩整个 Block(不是单列),服务端需配enable_http_compression=1和对应http_zlib_compression_level
示例(伪代码写 int32 列):
uint64_t rows = 100000;
write_varint(rows); // block 行数
write_varint(1); // 列数
write_string("x"); // 列名
write_string("Int32"); // 类型名
write_uint8(0); // 是否压缩(0=否)
for (int i = 0; i < rows; ++i) write_int32(data[i]);
如何组织写入节奏:Batch size、线程、连接复用
单个 HTTP 请求塞太多行(比如 > 50 万)会导致服务端 max_insert_block_size 拦截或 OOM;塞太少(65536–131072 行/请求。
多线程写入时,别为每个线程建独立连接——HTTP/1.1 的 keep-alive 在复用连接上比新建快 3–8 倍。用连接池(如 curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 0L))并限制总连接数 ≤ 8。
- 用无锁环形缓冲区(如
boost::lockfree::spsc_queue)做生产者-消费者解耦:业务线程 push 数据,IO 线程 pull 并攒批发送 - 避免在 IO 线程里做序列化——提前在业务线程把
Block二进制准备好,IO 线程只负责curl_easy_perform - 监控
X-ClickHouse-Summary响应头里的read_rows和written_rows,若二者差值大,说明服务端反压,需降速或加节点
容易被忽略的 ClickHouse 服务端调优项
客户端再快,服务端卡住也白搭。以下几项不调,写入到 30 万行/秒就明显抖动:
-
max_insert_block_size=1048576(默认 1024,太小) -
min_insert_block_size_rows=65536(触发后台合并前最小积压阈值) -
background_pool_size=16(默认 16,但写入密集时建议 ≥ 32) -
merge_tree_max_bytes_to_merge_at_once=268435456(防大合并阻塞写入) - 关闭
distributed_ddl_task_timeout相关检查(如果不用分布式表)
另外,ReplicatedReplacingMergeTree 表引擎在高写入下会因 ZooKeeper 同步成为瓶颈,纯写入场景优先选 ReplacingMergeTree 或 SummingMergeTree。
真实压测中,千兆网+SSD+16 核服务器,配合上述配置,单节点稳定写入 180 万行/秒(平均行宽 128 字节)是可达成的。但要注意:一旦开启副本或跨数据中心同步,吞吐必然断崖下跌——这不是客户端能解决的问题。


















