多线程读取数据时H2数据库阻塞问题及优化咨询
多请求下Spring Boot+H2应用性能优化方案
问题背景
Spring Boot 2.4.2 + H2 1.4.199内存数据库环境,单请求处理1500-2000条只读SELECT(由40-80个线程并行执行)响应正常,但5个请求并发时出现严重阻塞,线程在CacheLongKeyLIRS处阻塞,MVStore占用CPU资源最高。
优化方案
1. 减少单请求的查询总量
- 合并SQL查询:将多个独立的单条查询合并为批量查询或关联查询,比如用
IN子句一次性获取多条数据,或通过JOIN关联多张表减少查询次数,直接降低数据库的IO和CPU开销。 - 应用层缓存热点数据:使用Spring Cache(如Caffeine)对重复查询的热点数据做本地缓存,避免重复访问数据库。示例配置:
注意根据数据更新频率设置合理的缓存失效时间,保证数据一致性。@Cacheable(value = "hotData", key = "#id") public Data getHotData(Long id) { // 原数据库查询逻辑 }
2. 调整H2数据库参数
- 优化MVStore并发性能:在JDBC URL中添加
MULTI_THREADED=TRUE,开启MVStore的多线程优化,减少只读场景下的锁竞争:jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;LOG=0;CACHE_SIZE=131072;LOCK_MODE=0;UNDO_LOG=0;AUTOCOMMIT=TRUE;MULTI_THREADED=TRUE - 调整缓存策略:将
CACHE_TYPE改为SOFT_LRU,或根据服务器内存情况适当增大CACHE_SIZE(单位为页面数,默认每页4KB),减少缓存淘汰和锁竞争:jdbc:h2:mem:testdb;...;CACHE_TYPE=SOFT_LRU;CACHE_SIZE=262144 - 优化事务配置:将单请求内的批量只读查询放在只读事务中,减少自动提交的开销,在Spring中通过
@Transactional(readOnly = true)注解实现:@Transactional(readOnly = true) public List<Data> batchQuery(List<Long> ids) { // 批量查询逻辑 }
3. 优化请求内的线程模型
- 控制请求内线程数量:每个请求创建40-80个线程会加剧数据库连接和H2内部的锁竞争,建议根据服务器CPU核心数调整线程数(比如CPU核心数*2),或使用并行流并限制并行度:
// 自定义并行流的线程池,控制并发数 ForkJoinPool customPool = new ForkJoinPool(8); customPool.submit(() -> dataIds.parallelStream().map(this::queryData).collect(Collectors.toList())).join(); - 优化数据库连接池:调整HikariCP连接池参数,避免连接过多或频繁创建:
spring: datasource: hikari: maximum-pool-size: 16 minimum-idle: 4 connection-timeout: 30000 idle-timeout: 600000
4. 升级H2版本
H2 1.4.199属于旧版本,后续的1.4.200+或2.x版本对MVStore的并发锁、缓存机制做了大量性能优化,可尝试升级版本(注意2.x版本存在语法兼容性问题,需提前测试)。
5. 排查缓存阻塞根源
- 检查是否存在缓存穿透(频繁查询不存在的数据)导致缓存频繁失效,可添加空值缓存避免无效查询。
- 对于静态只读数据,设置永久缓存或更长的过期时间,减少缓存更新带来的锁竞争。
内容的提问来源于stack exchange,提问作者souleatzz
相关产品推荐
相关产品推荐

