Spring Boot中高效执行多个jdbcTemplate查询的最佳方案
下拉筛选预取接口的数据库查询优化方案
现有实现的问题
你当前采用的串行查询逻辑功能上没有问题,但接口总响应时间等于所有查询的耗时累加,且每一次查询都要单独经历数据库连接租用、网络传输、SQL执行的完整流程,当单条查询耗时较高时,接口整体响应速度会受明显影响。
可选的高效查询方案
- 单SQL合并查询
所有下拉选项查询都是无关联的只读聚合查询,完全可以通过UNION ALL把多个查询合并为一次数据库请求,把多次网络IO压缩为一次,是性能最高的方案。不同数据库语法略有差异,通用写法示例如下:
查询完成后只需要在代码中按SELECT 'colors' AS filter_key, colors AS filter_value FROM (SELECT DISTINCT colors FROM table1) t1 UNION ALL SELECT 'categories' AS filter_key, categories AS filter_value FROM (SELECT DISTINCT categories FROM table2) t2 UNION ALL SELECT 'weights' AS filter_key, weights AS filter_value FROM (SELECT DISTINCT weights FROM table3) t3;filter_key分组,把对应的值拆分到DTO的对应字段即可。这种方案额外开销最低,还能保证所有查询拿到的是同一个时间点的数据快照,不会出现数据不一致的问题。如果单类选项的结果集特别大,需要评估合并后的总数据量是否会带来额外的内存传输压力。 - 并行执行独立查询
如果不同查询的逻辑差异大、不适合合并为单条SQL,可以把互相无依赖的查询任务提交到线程池并行执行,最终接口总耗时取决于最慢的单条查询,而非所有查询耗时之和。
是否需要改为异步执行
没有绝对答案,根据实际场景判断即可:
- 如果所有单条查询耗时都在10ms以内,串行总耗时不超过50ms,完全没必要做异步改造。线程切换、多连接调度的额外开销反而会拉低性能,还会提升代码复杂度。
- 如果单条查询耗时较高、串行总耗时超过接口响应阈值(比如接口要求200ms内返回,串行执行需要300ms以上),并行改造的收益会非常明显。
注意:并行查询会同时占用多个数据库连接,必须提前评估数据库连接池的容量,避免出现连接不够用、请求排队等待连接的反效果。
Spring Boot 场景下的最佳实践
按照优先级从高到低选择实现方式:
- 优先做缓存优化
下拉选项类数据的变更频率通常极低,优先在Service层加本地缓存(比如Caffeine)或者分布式缓存,设置几分钟到几小时不等的过期时间,直接拦截重复的数据库查询,这是投入产出比最高的优化手段。同时给查询的字段建对应索引,避免distinct查询走全表扫描。 - 优先选择SQL合并方案
简单场景下直接用UNION ALL把多个查询合并为单条请求,代码改动小、性能稳定,没有额外的并发复杂度。 - 不适合合并SQL时,用CompletableFuture配合自定义线程池实现并行查询
不要直接使用@Async注解的默认线程池(无界队列、核心线程数配置不合理容易引发OOM),也不要自己手动new线程,提前配置好和数据库连接池容量匹配的业务线程池,示例实现:// 提前在配置类中定义好业务线程池,核心线程数不要超过数据库连接池的最大空闲连接数 @Resource private ThreadPoolExecutor filterQueryThreadPool; public Filters loadAllFilters(MapSqlParameterSource paramSource) { // 提交所有并行查询任务 CompletableFuture<List<String>> colorTask = CompletableFuture.supplyAsync( () -> namedParameterJdbcTemplate.queryForList(getColorsQuery, paramSource, String.class), filterQueryThreadPool ); CompletableFuture<List<String>> categoryTask = CompletableFuture.supplyAsync( () -> namedParameterJdbcTemplate.queryForList(getCategoriesQuery, paramSource, String.class), filterQueryThreadPool ); CompletableFuture<List<String>> weightTask = CompletableFuture.supplyAsync( () -> namedParameterJdbcTemplate.queryForList(getWeightsQuery, paramSource, String.class), filterQueryThreadPool ); // 等待所有任务执行完成后组装结果 try { Filters filters = new Filters(); filters.setColors(colorTask.get()); filters.setCategories(categoryTask.get()); filters.setWeights(weightTask.get()); return filters; } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); throw new BusinessException("加载筛选选项失败", e); } }
内容的提问来源于stack exchange,提问作者Gojo
相关产品推荐
相关产品推荐

