JPA分页查询随着每次后续调用而变慢



Project具有带有JPA的Spring Boot。我们有一个表车辆,记录超过1米。表有一个索引字段类型

我们有一个用例,我们希望按类型获取所有记录。对于每种类型,我们都会得到所有的车辆记录,然后是下一种类型,然后是第二种类型,依此类推

由于有1m+条记录,我们正在获取每种类型的记录,批量大小为1000。我们还应用了类型为column的筛选器。

VehicleDepository.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种类型的记录:

  1. A-0车辆
  2. B-100000辆
  3. C-0车辆
  4. D-0车辆
  5. E-0车辆

问题:

  1. 在这种情况下,当为A获取数据时,findByType在<100ms。这很好。

  2. 然而,当提取B时,LIMIT 1000 OFFSET 0的第一次提取需要约200ms。但从现在开始,随着OFFSET值的增加,时间也在增加。当LIMIT为1000,OFFSET为90000时,findByType需要6000-7000ms。

  3. 更令人困惑的是,在为B提取数据后,其余类型(C、D和E)每个都需要3000-4000ms,而它们有0个数据。

我不确定这里发生了什么。我在某个地方读到,正是因为OFFSET值高,该方法花费了这么多时间。但这并不能解释为什么这种方法对C、D和E.来说需要这么多时间

任何投入都将是有益的。感谢

编辑1:分析结果(Visual VM)

  1. SQL查询执行正常,几乎不需要150-200ms,即使对于高偏移值也是如此
  2. 这是出乎意料的,车辆集合在每次迭代后都会不断向其添加车辆记录(在profiler的内存部分中观察到了这一点)。我期望";活物体";count保持为最大1000,因为这是我们的限制大小。但在每次迭代后,它都会不断向其中添加1000条记录。即使在从profiler执行手动GC后,它也不会释放内存,直到for循环的所有迭代完成

从注释来看,问题似乎不在于分页查询本身,而在于它的使用方式和数据量对JVM的影响。提供的代码片段表明,您在同一VehicleService方法中多次调用vehicleRepository.findByType(type, pageable);,这意味着它们都在同一EntityManager/事务上下文中。JPA要求EntityManager上下文缓存通过它们读取的每个实体,以便它们可以监视和序列化对数据库所做的任何更改。如果你正在大量阅读实体,那么这就是构建——EntityManager旨在代表工作单元,而不是像那样长寿。

解决方案是将每个"批次"分解为自己的事务上下文,并对每个车辆类型进行调用。

或者,您可以获取EntityManager实例的句柄。处理完实体后,调用EntityManager.clear(),让它释放对其中所有托管实体的引用,如果没有对它们的应用程序引用,则允许对它们进行垃圾收集。

Chris说了一些正确的话:可能是您的应用程序不知道上次查询";B";,发生的情况是(页面大小1000):

您请求页面0:查找匹配条目并将它们添加到结果集中。一旦结果集的大小为1000,就返回它。

您请求第1页:查找(!)并跳过前1000个匹配条目。取匹配的条目1001到2000,将它们添加到结果集中并忽略它

您请求第2页:查找(!)并跳过前2000个匹配条目。取匹配的条目2001到3000,将它们添加到结果集中并忽略它

等等

因此,基本上数据库会多次执行查询,每次都会增加总查询时间,因为数据库不知道上次的剩余时间。一种解决方案是以某种方式将最后获取的id(主键)传递给查询,然后从那里开始(... AND id > :id)。也许你是

我编写了一个示例应用程序来测试您的发现。我的车辆表中目前有约723k个条目。数据库和应用程序在我的本地机器上运行(页面大小1000):

  1. 查询A(0个条目)耗时约10ms
  2. 查询B(0个条目)大约花费了2200ms
  3. 查询C(0个条目)耗时约10ms
  4. 查询D(0个条目)耗时约10ms
  5. 查询E(0个条目)耗时约10ms

因此,我不能重现您的问题。也许您可以将代码简化为尽可能简单的并与我们共享(或者自己找到瓶颈)。

我把我的上传到了我的Github存储库。

结果是:

A: 185ms
B: 2139ms
B: 2007ms
B: 1863ms
B: 1930ms
C: 2ms
D: 3ms
E: 2ms
A: 1ms
B: 2020ms
B: 2044ms
B: 2006ms
B: 2053ms
B: .. same average values all over

还有一件事,如果你的数据库中有很多记录,但只有少量不同类型的记录,那么索引不会有多大帮助。某些SQL优化器可能会忽略索引并执行完整的表扫描,因为索引基数可能太低。

最新更新