如果我在sql而不是’in’中使用’或’,那么Java会出现堆空间错误

我在我的项目中使用spring和hibernate,前几天我发现Dev环境因Java堆空间异常而崩溃.在使用一些堆分析工具和visual vm进行一些初步分析之后,我发现问题出在一个选择SQL查询上.我以不同的方式重写了SQL,解决了内存问题...

我在我的项目中使用spring和hibernate,前几天我发现Dev环境因Java堆空间异常而崩溃.在使用一些堆分析工具和visual vm进行一些初步分析之后,我发现问题出在一个选择SQL查询上.我以不同的方式重写了SQL,解决了内存问题.但现在我不确定为什么以前的SQL导致了内存问题.
注意:该方法位于DAO中,并在批量大小为800的while循环中调用,直到拉出所有数据.表大小约为2000万行.
对于每个调用,都会创建并销毁新的hibernate会话.

以前的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会话也可能会在数据库上有很多会话.我相信你应该只使用一个会话,除非有特殊原因.创建和销毁不需要的会话只会在服务器和网络上创建不必要的流量.
如果您以串行方式运行此代码,那么为什么要删除会话,何时可以将其重新用于下一批?您可能有正当理由,但必须提出问题.

在第二个语句中,创建bookIds数组会再次占用JVM堆中的RAM,并且SQL中的(:bookIds)部分中的i.journalId仍然很长.没有以前那么糟糕,但我认为还是太长了.
做以下事情你会好得多:

使用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而不是’in’中使用’或’,那么Java会出现堆空间错误

基础教程推荐