ClickHouse Java客户端复用实例插入10次后遇执行超时问题求助
问题分析
你遇到的10条数据插入成功后第11条超时的问题,核心原因是Apache HttpClient5的默认连接池最大连接数为10:当循环发起第11次请求时,连接池已无可用连接,等待超时后抛出Code:159异常。同时你当前的单条插入方式会频繁占用连接,进一步加剧了连接池耗尽的问题。
解决方案
1. 配置HttpClient连接池实现连接复用
手动配置HttpClient5的连接池参数,传递给ClickHouseClient,控制连接的最大数量、空闲回收规则,避免连接耗尽。
调整后的Java代码如下:
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import java.util.HashMap; import java.util.UUID; public class ClickHouseBatchInsert { public static void main(String[] args) { // 配置连接池 PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); // 设置总最大连接数 connManager.setMaxTotal(50); // 设置单路由最大连接数 connManager.setDefaultMaxPerRoute(20); // 空闲连接自动回收超时 connManager.setValidateAfterInactivity(TimeValue.ofSeconds(30)); // 配置Socket参数 SocketConfig socketConfig = SocketConfig.custom() .setSoTimeout(Timeout.ofSeconds(10)) .setSoKeepAlive(true) .build(); connManager.setDefaultSocketConfig(socketConfig); // 构建带连接池的HttpClient CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(connManager) .build(); // 配置ClickHouse客户端 HashMap<String, String> options = new HashMap<>(); options.put("user", "user"); options.put("password", "pass"); ClickHouseNode server = ClickHouseNode.of("my-clickhouse-host", options); // 复用ClickHouseClient实例 try (ClickHouseClient client = ClickHouseClient.builder() .nodeSelector(ClickHouseNodeSelector.of(ClickHouseProtocol.HTTP)) .httpClient(httpClient) .build()) { // 批量插入:合并所有数据为一个请求 StringBuilder batchSql = new StringBuilder("INSERT INTO mytable VALUES "); for (int i = 0; i < 11; i++) { String id = UUID.randomUUID().toString(); String field1 = "field1" + i; String field2 = "field2" + i; String field3 = "field3" + i; String field4 = "field4" + i; if (i > 0) { batchSql.append(", "); } batchSql.append(String.format("('%s', '%s', '%s', '%s', '%s')", id, field1, field2, field3, field4)); } // 执行批量插入 client.read(server) .write() .format(ClickHouseFormat.CustomSeparated) .query(batchSql.toString()) .executeAndWait(); System.out.println("11条数据全部插入成功"); } catch (Exception e) { e.printStackTrace(); } } }
2. 关键优化点说明
- 连接池配置:通过
PoolingHttpClientConnectionManager自定义连接数上限,避免默认10个连接的限制;同时配置空闲连接自动回收,防止连接泄漏。 - 复用ClickHouseClient:通过try-with-resources管理Client生命周期,确保资源合理释放;注入自定义HttpClient后,所有请求都会复用连接池中的连接。
- 批量插入:将多条INSERT合并为一个请求,大幅减少连接占用次数,既提升插入效率,又从根源上避免连接池被频繁请求占满。
额外安全优化:避免SQL注入
当前代码用字符串拼接SQL存在注入风险,建议使用参数化批量插入:
try (ClickHouseClient client = ClickHouseClient.builder().httpClient(httpClient).build(); ClickHouseSession session = client.connect(server).get()) { String insertSql = "INSERT INTO mytable VALUES (?, ?, ?, ?, ?)"; try (ClickHouseStatement stmt = session.createStatement()) { for (int i = 0; i < 11; i++) { stmt.setObject(1, UUID.randomUUID().toString()); stmt.setObject(2, "field1" + i); stmt.setObject(3, "field2" + i); stmt.setObject(4, "field3" + i); stmt.setObject(5, "field4" + i); stmt.addBatch(); } stmt.executeBatch(); } }
这种方式既避免了语法错误,又提升了请求安全性。
内容的提问来源于stack exchange,提问作者Octavio Cunha
相关产品推荐
相关产品推荐

