我在我的项目中使用了 spring 和 hibernate,几天前我发现 Dev 环境由于 Java 堆空间不足异常而崩溃。在使用一些堆分析工具和可视化虚拟机进行一些初步分析后,我发现问题出在 one select SQL 查询上。我以不同的方式重写了 SQL,解决了内存问题。但是现在我不确定为什么以前的 SQL 会导致内存问题。 注意:该方法在 DAO 内部,并在批处理大小为 800 的 while 循环中调用,直到拉取所有数据。表大小约为 2000 万行。 对于每次调用,都会创建和销毁一个新的 hibernate session 。
以前的 SQL:
@Override
public List<Book> getbookByJournalId(UnitOfWork uow,
List<Journal> batch) {
StringBuilder sb = new StringBuilder();
sb.append("select i from Book i where ( ");
if (batch == null || batch.size() <= 0)
sb.append("1=0 )");
else {
for (int i = 0; i < batch.size(); i++) {
if (i > 0)
sb.append(" OR ");
sb.append("( i.journalId='" + batch.get(i).journalId() + "')");
}
sb.append(")");
sb.append(
" and i.isDummy=:isNotDummy and i.statusId !=:BookStatus and i.BookNumber like :book ");
}
Query query = uow.getSession().createQuery(sb.toString());
query.setParameter("isNotDummy", Definitions.BooleanIdentifiers_Char.No);
query.setParameter("Book", "%" + Definitions.NOBook);
query.setParameter("BookStatus", Definitions.BookStatusID.CLOSED.getValue());
List<Book> bookList = (List<Book>) query.getResultList();
return bookList;
}
重写的 SQL:
@Override
public List<Book> getbookByJournalId(UnitOfWork uow,
List<Journal> batch) {
List<String> bookIds = new ArrayList<>();
for(Journal J : batch){
bookIds.add(J.getJournalId());
}
StringBuilder sb = new StringBuilder();
sb.append("select i from Book i where i.journalId in (:bookIds) and i.isDummy=:isNotDummy and i.statusId !=:BookStatus and i.BookNumber like :Book");
Query query = uow.getSession().createQuery(sb.toString());
query.setParameter("isNotDummy", Definitions.BooleanIdentifiers_Char.No);
query.setParameter("Book", "%" + Definitions.NOBook);
query.setParameter("BookStatus", Definitions.BookStatusID.CLOSED.getValue());
query.setParameter("specimenNums",specimenNums);
query.setParameter("bookIds", bookIds);
List<Book> bookList = (List<Book>) query.getResultList();
return bookList;
}
最佳答案
当您创建动态 SQL 语句时,您会错过数据库缓存语句、索引甚至整个表以优化数据检索的能力。也就是说,动态 SQL 仍然是一个实用的解决方案。 但是您需要通过非常有效地使用内存来成为应用程序和数据库服务器的好公民。对于需要扩展到 2000 万行的解决方案,我建议更多地使用基于磁盘的方法,使用尽可能少的 RAM(即避免使用数组)。
从第一个语句中我可以看出问题如下:
最多 800 个 OR
条件可以添加到每个批处理的第一个语句中。这使得 SQL 语句非常长(不好)。我相信 [如果我错了请纠正我] 需要缓存在 JVM 堆中,然后传递给数据库。
Java 可能不会立即从堆中释放此语句,并且垃圾收集可能太慢而无法跟上您的代码,从而增加了 RAM 使用量。当您的代码正在运行时,您不应该依赖它来进行清理。
如果您并行运行此代码, hibernate 上的许多 session 也可能存在数据库上的许多 session 的风险。我相信您应该为此只使用一个 session ,除非有特定原因。创建和销毁不需要的 session 只会在服务器和网络上产生不必要的流量。 如果您连续运行此代码,那么为什么要删除 session ,当您可以在下一批中重用它时?您可能有正当理由,但必须提出问题。
在第二个语句中,创建 bookIds
数组再次使用了 JVM 堆中的 RAM,并且 SQL 的 where i.journalId in (:bookIds)
部分仍然会很长。没有以前那么糟糕了,但我觉得还是太久了。
执行以下操作会更好:
在数据库上创建一个表,其中包含 batchNumber
、bookId
以及一些元数据,例如标志或时间戳。使用静态语句将 Book
表加入到您的新表中,并将 batchNumber 作为新参数传入。
create table Batch
(
id integer primary key,
batchNumber integer not null,
bookId integer not null,
processed_datetime timestamp
);
create unique index Batch_Idx on Batch (batchNumber, bookId);
-- Put this statement into a loop, or use INSERT/SELECT if the data is available in the database
insert into Batch batchNumber values (:batchNumber, :bookId);
-- Updated SQL statement. This is now static. Note that batchNumber needs to be provided as a parameter.
select i
from Book i
inner join Batch b on b.bookId = i.journalId
where b.batchNumber = :batchNumber
and i.isDummy=:isNotDummy and i.statusId !=:BookStatus and i.BookNumber like :Book;
关于如果我在 sql 中使用 'or' 而不是 'in',则 Java 堆空间不足错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47421765/