Spring Boot结合JDBC时,如何并行或批量执行多表多列的Count统计查询?
Spring Boot结合JDBC时,如何并行或批量执行多表多列的Count统计查询?
兄弟,你的问题我太懂了——100多次Count查询串行跑确实慢,而且用Reactor的时候还踩了连接池的坑,咱们一步步来解决。
首先,优先推荐用批量SQL的方式,这比并行执行效率高得多,因为本质上只需要和数据库做一次交互,不用占多个连接,也减少了网络开销。
方案一:合并成单条批量SQL查询
思路很简单:把所有需要统计的Count都拼到一个SQL里,用UNION ALL来整合结果。比如你要统计不同表不同列的Count,可以写成这样的SQL:
SELECT 'table1_col1' as target, COUNT(col1) as count FROM table1 UNION ALL SELECT 'table1_col2' as target, COUNT(col2) as count FROM table1 UNION ALL SELECT 'table2_col1' as target, COUNT(col1) as count FROM table2 ...
用Spring JDBC的话,你可以动态拼接这个SQL,然后一次性拿到所有结果:
// 假设你有一个封装表名和列名的类,比如CountTask List<CountTask> countTasks = ...; // 你的100+统计任务 StringBuilder sqlBuilder = new StringBuilder(); for (int i = 0; i < countTasks.size(); i++) { CountTask task = countTasks.get(i); if (i > 0) { sqlBuilder.append(" UNION ALL "); } // 这里一定要注意防SQL注入!别直接拼用户传入的表名/列名,先做白名单验证或者用元数据校验 sqlBuilder.append(String.format("SELECT '%s_%s' as identifier, COUNT(%s) as count FROM %s", task.getTableName(), task.getColumnName(), task.getColumnName(), task.getTableName())); } // 用JdbcTemplate执行查询 List<Map<String, Object>> rawResults = jdbcTemplate.queryForList(sqlBuilder.toString()); // 转换成你需要的Count对象列表 List<Count> countList = rawResults.stream() .map(map -> new Count( (String) map.get("identifier"), ((Number) map.get("count")).longValue() )) .collect(Collectors.toList());
⚠️ 重要提醒:动态拼接表名和列名的时候,一定要做SQL注入防护——比如提前用数据库元数据验证这些表和列是否存在,或者维护一个允许的白名单,绝对不能直接拼接用户输入的未校验值。
方案二:控制并发的并行查询(适合无法批量的场景)
如果因为某些原因没法用批量SQL,必须并行执行,那你之前的代码问题出在无限制地触发所有异步任务,瞬间把数据库连接池占满了,导致连接失败。
正确的做法是用Reactor的flatMap来控制并发数,让并行的请求数不超过你的数据库连接池大小:
// 假设countList是你的统计任务列表 List<Count> countResults = Flux.fromIterable(countList) // flatMap的第二个参数就是最大并发数,建议设置成和连接池大小一致(比如10-20,看你的配置) .flatMap(c -> Mono.fromCallable(() -> countRepo.selectQuery(c)) .subscribeOn(Schedulers.boundedElastic()), 10) .collectList() .block(); // 如果是同步需要结果就用block,异步场景用subscribe处理
然后还要调整你的数据库连接池配置,比如在application.properties里:
# Hikari连接池配置,根据你的服务器和数据库性能调整 spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.connection-timeout=30000
为什么这么做?因为Schedulers.boundedElastic()会给每个阻塞的JDBC操作分配单独的线程,但数据库连接池的数量是有限的——如果并发数超过连接池大小,就会出现拿不到连接的报错,所以必须用flatMap的并发参数来限制同时执行的查询数。
两种方案对比
- 批量SQL:性能最优,数据库压力最小,代码也相对简洁,优先考虑。
- 并行查询:适合每个查询逻辑差异大、没法合并的场景,但要严格控制并发数,避免连接池耗尽,性能上不如批量SQL。
备注:内容来源于stack exchange,提问作者Daniel Bristol
相关产品推荐
相关产品推荐

