本节摘要:本节讲应用怎么连 ClickHouse:原生 TCP 驱动(快)、HTTP 接口(通用)、各语言驱动选择,以及连接池和并发控制的最佳实践。
阅读完本节,你应当能够:
ClickHouse 有两套协议:
原生协议比 HTTP 快(二进制压缩、连接复用好),但只有 ClickHouse 专属驱动支持。HTTP 通用但略慢。选驱动时先看有没有你语言的原生驱动,没有就用 HTTP。
| 协议 | 端口 | 特点 | 适用 |
|---|---|---|---|
| 原生 TCP | 9000 | 快、二进制 | 有原生驱动的语言 |
| HTTP | 8123 | 通用、略慢 | 任何语言、JDBC、curl |
生产 Java 应用大多用 JDBC,配合连接池(HikariCP)。注意 JDBC 走 HTTP,每个查询一个 HTTP 请求,高频小查询开销不小。
数据分析用 clickhouse-connect 够了;高频写入或查询用 clickhouse-driver。
database/sql 接口。ClickHouse 的并发模型是"少而精"(第 2 章讲过),客户端侧要配合:
# Python 批量写入示例 import clickhouse_connect client = clickhouse_connect.get_client(host='ch', port=8123) # 一次插入一批 client.insert('events', rows_data, column_names=['event_time','user_id','city'])
| 客户端实践 | 建议 |
|---|---|
| 连接池大小 | 按并发查询数设,别过大 |
| 并发查询 | 不超过 CPU 核数太多 |
| 写入 | 批量,单次 ≥1000 行 |
| 重试 | 对瞬时错误重试,对逻辑错误别重试 |
| 超时 | 设合理查询超时,防慢查询拖死 |
有人把 ClickHouse 当 MySQL,每个用户请求都查一次,结果并发把集群拖垮。ClickHouse 不擅长高并发小查询,适合低频大查询。高频点查场景用 Redis 缓存兜底,ClickHouse 做后台聚合。
# 错误:一行一写,灾难 for row in rows: client.insert('events', [row], ...)
单行写入制造海量小 part,合并跟不上。改成批量:
# 正确:批量写 client.insert('events', rows, ...)
应用查询用 SELECT * 读所有列,把列存优势废了。永远显式列清单,只读需要的列。
没设查询超时,慢查询一直挂着占资源。客户端和服务器都要设超时。
⚠️ 常见坑:把 ClickHouse 当通用数据库用——高频小查询、单行写入、SELECT *、不设超时,这四样凑齐,再好的集群也扛不住。ClickHouse 有它的脾气,顺着它的脾气用才省心。
💡 关键直觉:选驱动看协议(有原生用原生,没原生用 HTTP),用驱动要配合 ClickHouse 的并发模型(少而精、批量写、显式列、设超时)。客户端侧的坏习惯比服务端配置更毁性能。
最后一节把这些串成架构最佳实践,给你一个端到端的设计参考。
驱动的选择不只是"哪个库能用",还牵涉到数据格式、批处理、错误处理。Python 场景两个驱动各有定位,选型标准可以这样把握:
import clickhouse_connect # 分析场景:clickhouse-connect(HTTP,简单) client = clickhouse_connect.get_client(host='ch1', port=8123, user='analyst', password='pwd') result = client.query("SELECT city, count() FROM events GROUP BY city LIMIT 10") for row in result.result_rows: print(row)
高吞吐写入场景,用原生协议驱动(如 clickhouse-driver)并用批量写入接口,避免一行一次 insert。批量写入是 ClickHouse 客户端最重要的实践,没有之一:
import time import clickhouse_driver client = clickhouse_driver.Client(host='ch1') # 攒批:一次插 10000 行 rows = [(int(time.time()), user_id, city, amount) for user_id, city, amount in data] client.execute( 'INSERT INTO events (event_time, user_id, city, amount) VALUES', rows, types_check=True, # 开启类型检查,提前暴露类型不匹配 )
批量写入的两个额外注意点:一是 types_check=True 能帮你在开发期就发现类型错误,而不是上线后由服务端拒绝;二是写入超时要单独设,批量插入耗时可能超过查询超时。这两种情况都属于"客户端侧最常见的坑",提前配好比出事后排查省心。
Java 场景走 JDBC 时同理:用 HikariCP 做连接池,但连接池大小按并发查询数设(比如核数的一半),别盲目调大;写操作用 PreparedStatement 配 addBatch() 批量提交。驱动只是管道,顺着 ClickHouse"少而精、批量写"的脾气用它,性能才有保障。