【发布时间】:2022-01-25 14:46:16
【问题描述】:
项目具有带有 JPA 的 Spring Boot。我们有一个表 vehicle 有 100 万多条记录。表有一个索引字段type。
我们有一个用例,我们想按类型获取所有记录。对于每种类型,我们获取所有车辆记录,然后是下一个类型,然后是下一个,依此类推。
由于有 1m+ 条记录,我们以批量大小为 1000 的方式获取每种类型的记录。我们还应用了带有类型列的过滤器。
VehicleRepository.java
Page<VehicleRecord> findByType(String type, Pageable pageable);
VehicleService.java
for (String type: vehicleTypes) {
Pageable pageable = PageRequest.of(0, 1000, Sort.by("updated_at").ascending());
Page<VehicleRecord> vehicles = null;
do {
vehicles = vehicleRepository.findByType(type, pageable);
// do something with vehicles
pageable = pageable.next();
} while (vehicles.hasNext());
}
为了便于理解,假设有5种记录:
- A - 0 辆车
- B - 100000 辆
- C - 0 辆车
- D - 0 辆车
- E - 0 辆车
问题:
-
在这种情况下,当为 A 获取数据时,findByType 在
-
当为 B 获取时,首先使用 LIMIT 1000 OFFSET 0 获取大约需要 200 毫秒。但从这里开始走下坡路,随着OFFSET值的增加,时间也随之增加。到 LIMIT 为 1000 且 OFFSET 为 90000 时,findByType 需要 6000-7000 毫秒。
-
更令人困惑的是,在为 B 获取数据后,其余类型(C、D 和 E)在它们有 0 个数据时每个需要 3000-4000 毫秒。
我不确定这里发生了什么。我在某处读到,由于高 OFFSET 值,该方法花费了这么多时间。但这并不能解释为什么该方法对 C、D 和 E 需要这么多时间。
任何输入都会有所帮助。谢谢
编辑 1:分析结果(Visual VM)
- SQL 查询正在正常执行,它们几乎不需要 150-200 毫秒,即使对于高偏移值也是如此。
- 这是出乎意料的,车辆集合在每次迭代后不断向其添加车辆记录(在分析器的内存部分中观察到这一点)。我希望“活动对象”计数保持在最大 1000,因为这就是我们的限制大小。但在每次迭代之后,它会不断向其中添加 1000 条记录。即使在从分析器执行手动 GC 之后,它也不会释放内存,直到 for 循环的所有迭代都完成。
【问题讨论】:
-
可能实体管理器没有清除?也许有人试图在缓存中查找东西。这是在单笔交易中完成的吗?
-
向后循环时会发生什么? (e -> a)
-
2 美分:比较偏移量和键集分页怎么样?也许这也可以解释一些放缓? use-the-index-luke.com/no-offset
-
@Chris 我尝试分析,查询运行正常,不需要太多时间,总是
-
我不确定我是否理解您对结果的看法 - 听起来您的查询本身没有问题。听起来不错!我不知道活动对象指的是什么,或者您的设置 - 这是您读入 JVM 的对象数量,并且在您查询后被保留?分页很棒,但您可能对所有页面使用相同的 entityManager 实例 - JPA 要求它们由持久性单元“管理”,直到 EM 完成。您在 VehicleService 中显示的循环并不能清楚地说明您在做什么。可能只需要一个 em.clear()
标签: java spring-boot jpa spring-data-jpa garbage-collection