如何在 Spring Boot 中以内存高效的方式迭代 MySQL 中的大量记录
Posted
技术标签:
【中文标题】如何在 Spring Boot 中以内存高效的方式迭代 MySQL 中的大量记录【英文标题】:How to iterate over large number of records in MySQL with memory efficient manner in Spring Boot 【发布时间】:2019-12-20 01:35:06 【问题描述】:我想使用 findAll
从表中获取所有记录并对每条记录进行一些处理,但我不确定如果记录数量巨大(如数百万)是否会出现内存问题。
我研究了Pageable
,但我不确定如何使用Pageable
方法迭代所有数据。是否甚至可以一次获取少量记录并处理它们并再次获取它们,直到处理完所有记录?
什么会更好?使用findAll()
方法或Pageable方法获取Iterable中的所有记录?
【问题讨论】:
您打算更新记录还是只读取数据? 您似乎使用了错误的工具。一旦你需要扩大规模,问题空间变成了大数据问题——你应该考虑使用大数据工具,即 spark、kafka、... @MaciejKowalski 我打算只读取数据并对其进行一些处理。 【参考方案1】:如果它可以是数百万..
1) 不要使用findAll()
并检索实际托管实体的列表。如果您只需要读取数据,请使用投影查询以及 Spring Data JPA 投影接口。这将绕过持久化上下文并节省大量时间和内存。
2) 使用分页(节省内存)并确保在新事务中进行每个调用 (@Transactional(propagation = REQUIRES_NEW)
)。这将允许其他事务不会永远挂起,如果您不使用分页并且只触发了一个,可能会出现这种情况,请给我全部,查询。
3) 它看起来也适合过夜批处理作业。考虑一下。
【讨论】:
【参考方案2】:如果实体很多,不要使用 findAll。
如果你想使用分页,你可以这样做:
Pageable pageRequest = PageRequest.of(0, 200);
Page<Qmail> onePage = repository.findAll(pageRequest);
while (!onePage.isEmpty())
pageRequest = pageRequest.next();
//DO SOMETHING WITH ENTITIES
onePage.forEach(entity -> System.out.println(entity.getId()));
onePage = repository.findAll(pageRequest);
【讨论】:
你不认为 onePage = repository.findAll(pageRequest); 这会再次获取相同的结果吗?由于 pageRequest 仍然有页码 0 和大小 200? 不,因为有一个pageRequest.next()
将移至下一页
pageRequest.next()
将返回 Pageable 而不是 PageRequest。所以这种方法现在会失败。但是,您可以将最后一行更改为 onePage = repository.findAll(pageRequest.nextPageable());
。它应该可以工作。
使用 while (!onePage.isLast()) 而不是 while (!onePage.isEmpty()),这样会节省最后一个额外的循环【参考方案3】:
自 Spring Data 1.8 起,您可以Stream
超过结果。
Stream<Record> findAll();
重要的是在这里添加一个QueryHint
关于数据库的获取大小。如果设置它在内部使用页面流过结果。
将此用于 mysql 数据库:
@QueryHints(value = @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE, value = "-2147483648"))
Stream<Record> findAll();
对于非 MySQL 数据库,您可以使用 fetch size:
@QueryHints(value = @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE, value = "5000"))
Stream<Record> findAll();
而且,如果您不更新/删除记录,请不要忘记将您的事务设置为只读:
@Transactional(readOnly = true)
【讨论】:
【参考方案4】:您需要的是批量读取数据并处理每个数据,并且可能会在其他地方保持不变或从中生成报告。
这是 ETL 用例。
这个案例可以用Spring Batch,可以很好的处理。
Reader 一次读取一个数据并在处理器中处理它。 Writer 将根据您设置的块/批量大小持久化或生成报告。
这样您就不会在内存中保存大量数据。
【讨论】:
以上是关于如何在 Spring Boot 中以内存高效的方式迭代 MySQL 中的大量记录的主要内容,如果未能解决你的问题,请参考以下文章
在 Spring Boot 中以编程方式加密/解密数据库密码
在 Spring Boot Transaction 中以只读方式执行 RxJava observable?
在 Spring Boot 应用程序中以编程方式重新启动 HikariPool?