【发布时间】:2021-01-04 14:22:38
【问题描述】:
我想要一页来自 Oracle 数据库表的过滤数据,但我有一个可能返回数千万条记录的查询,因此将其全部拉入内存是不可行的。我需要以无法通过 SQL 完成的方式过滤记录,并返回一页记录。换句话说,分页部分必须在过滤之后完成。
所以,我尝试使用 Hibernate 的 ScrollableResults,认为这将是一种一次只提取块并遍历它们的方法。所以,我创建了它:
ScrollableResults results = query.setReadOnly(true)
.setFetchSize(500)
.setCacheable(false)
.scroll();
...然而,它似乎将所有内容都拉入内存(每个查询拉入 2.5GB)。我见过another question 并尝试了一些建议,但大多数似乎是特定于MySQL 的,而且我使用的是Oracle 19 驱动程序(例如Integer.MIN_VALUE 在Oracle 驱动程序中被完全拒绝为获取大小)。
有人建议使用无状态会话(我正在使用没有无状态选项的EntityManager),但我的想法是,如果我们不获取很多记录(因为我们只想要200 条过滤记录),为什么 Hibernate 会在内存中保留数百万条记录,即使我们从未滚动过它们?
我很清楚,我不明白 Hibernate 如何/为什么将事物拉入内存,或者如何让它停止这样做。鉴于上述限制,有关如何防止它这样做的任何建议?
我要尝试的一些事情:
- 不同的滚动模式。也许麻木不仁或向前只是阻止 Hibernate 需要把所有东西都拉进去?
- 获得页面后清除会话。我正在关闭会话(在
ScrollableResults和EntityManager中都使用close()),但也许明确的clear()会有所帮助?
【问题讨论】:
-
这些比 Hibernate 更依赖于数据库,因此您需要搜索 Oracle 特定问题。让 fetchsize 与 Postgres 一起正常工作也很棘手。
-
如果这是一个耗费资源/时间的“繁重”查询,那么将所有内容抓取到内存中可能还不错。但是,如果您必须使用 Oracle 对每个页面重复查询,一种常见的方法是在查询中使用
ROW_NUMBER根据分组条件对每一行进行编号。数据,然后过滤 ROW_NUMBER 的值介于两者之间与您的页面相对应的上记录编号。不利的一面是,如果基础数据发生更改以影响结果,您可能会发现您“跳过”或“重复”行。 -
我一直调试到 JDBC 调用。 Oracle 驱动程序发回的
ResultSet可以适当地滚动,并且其中有一些标志,指示它是否已获取所有内容以及已获取多少记录。在调试器中很清楚驱动程序已经完成了它的工作。
标签: java oracle hibernate jpa entitymanager