clickhouse-driver同步写入卡住异步主线程,因其所有方法均为阻塞式,不支持await;须用线程池或改用aioclickhouse等异步客户端。

为什么 clickhouse-driver 的同步写入会卡住异步主线程?
因为 clickhouse-driver 默认使用 http.client 或原生 TCP socket,所有操作(包括 execute 和 execute_iter)都是阻塞的。哪怕你把代码包在 async def 里,只要调用它的方法,整个协程就会被挂起——它根本不认识 await。
常见现象:用 asyncio.gather 并发写入多张表,结果实际是串行执行,CPU 利用率低,吞吐上不去。
关键判断:不能直接 await 原生 driver 的任何方法;必须用线程池或换支持异步的客户端。
用 aioclickhouse 替代时要注意哪些兼容性问题?
aioclickhouse 是基于 aiohttp 的纯异步封装,接口尽量对齐 clickhouse-driver,但仍有几处必须调整:
立即学习“Python免费学习笔记(深入)”;
-
Client初始化参数不支持compression=True,启用了会报AttributeError: 'Client' object has no attribute 'compress_block' -
execute_iter返回的是AsyncGenerator,不能直接 for 循环,得用async for - 批量插入必须用
execute+data参数,不能用execute_batch(该方法不存在) - 连接字符串里的
secure=True对应https://,若服务端没配 TLS,得显式设secure=False,否则连接超时无明确错误提示
示例写法:
import aioclickhouse
client = aioclickhouse.Client('http://localhost:8123')
await client.execute('INSERT INTO logs (ts, msg) VALUES', data=[(1717023456, 'ok')])
大批量写入时,execute 和 execute_iter 性能差多少?
差别主要在内存占用和流控能力,不是速度本身。两者底层都走 HTTP POST,但行为不同:
-
execute把全部数据序列化成一个请求体发出去,适合单次 ≤ 10 万行、总大小 max_insert_block_size 或内存 OOM -
execute_iter按块分批发送(默认每块 1024 行),适合持续写入或数据源是生成器/文件流的场景;但要注意它返回的是异步迭代器,必须消费完,否则连接不释放 - 实测 50 万行写入,
execute耗时约 1.2s,execute_iter约 1.4s(含额外序列化开销),但后者 RSS 内存稳定在 25MB,前者峰值冲到 220MB
建议:日志类场景优先用 execute_iter;ETL 批处理且可控数据量,用 execute 更简洁。
如何避免 ConnectionResetError 和 ServerDisconnectedError?
这两个错误高频出现在高并发短连接场景下,本质是 ClickHouse HTTP 接口的连接复用机制和客户端不匹配:
-
aioclickhouse默认禁用连接池(pool_size=0),每次请求新建 TCP 连接,服务端来不及回收导致ConnectionResetError - 解决办法是显式启用连接池:
Client(..., pool_size=10),并配合keep_alive_timeout=30(需服务端keep_alive_timeout≥ 此值) - 如果 ClickHouse 配置了
max_concurrent_queries=100,但你的并发协程数超过此值,会触发ServerDisconnectedError—— 这不是网络问题,是服务端主动断连,得调小客户端并发数或加大服务端限制 - 别忽略超时设置:
connect_timeout=5、send_timeout=30、receive_timeout=30必须显式传,否则 aiohttp 默认值可能不符合 ClickHouse 实际响应节奏
最容易被忽略的一点:ClickHouse 的 http_connection_timeout 默认是 1 秒,远低于客户端设置,务必在服务端 config.xml 中调大,否则连接还没建好就被关了。



















