将 Big Derby 嵌入数据库迁移到 HSQLDB 抛出 java.lang.OutOfMemoryError: Java heap space

Posted

技术标签:

【中文标题】将 Big Derby 嵌入数据库迁移到 HSQLDB 抛出 java.lang.OutOfMemoryError: Java heap space【英文标题】:Migrating a Big Derby embebed database to HSQLDB throw java.lang.OutOfMemoryError: Java heap space 【发布时间】:2019-04-11 12:38:20 【问题描述】:

我正在尝试在 Spring Boot 服务中将大数据库从 derby 迁移到 HSQLDB,例如 10 列的几个表中的 1.5M regs。我正在检查 VisualVM; byte 和 char 消耗大量内存。但时间上最大的差异是在德比比赛中。

有时会在此处抛出错误,但有时会在其他控制器中抛出错误。我不想触摸所有文件来添加我的 catchOutofMemory 以重新启动。

以下是我的代码的一个版本,块注释显示了进程的简历:

run()//thread inside static function.
    while(keepMigrating)
        keepMigrating=Migrate();
    

private static boolean Migrate(JdbcTemplate derby,JdbcTemplate hsql)
    int regs = 100000;
    PreparedStatement statement = null;
    ResultSet rs = null;
    PreparedStatement statementHSQL = null;
    try 
        for (String table : tables) //tables contains all tables to migrate
        //check how many registers left asd asign to cant, if cant is 0 the empty is true.
        PreparedStatement statementUpd[];
            while (!empty) 
                if (trys <= 0) throw new Exception("redo");
                //check how many registers left asd asign to cant, if cant is 0 the empty is true and out of bucle and ready to check next table
                /*
                *Next process resume as:
                *fetch data from derby that hasnt been migrated limited by cant
                *create a batch to insert in hsql
                *create a update for derby
                *create a delete in case someting goes wrong
                *excecute insert and update, if someting in batch fail delete the entry in migrate table
                *reduce regs to get out of migrate method at some ponint.
                */
                statement = derby.getDataSource().getConnection().prepareStatement(
                MessageFormat.format(select_all_migrate_false_and_fetch_cant,table));
                statementUpd = new PreparedStatement[cant];
                ArrayList<String> deleteIds = new ArrayList<>();
                StringBuilder columnNames = new StringBuilder();
                StringBuilder updateSQL = new StringBuilder();
                StringBuilder bindVariables = new StringBuilder();
                try 
                    ResultSetMetaData meta = rs.getMetaData();
                    for (int i = 1; i <= meta.getColumnCount(); i++) 
                        if (!meta.getColumnName(i).equals("MIGRATED")) 
                            if (i > 1) 
                                columnNames.append(", ");
                                bindVariables.append(", ");
                            
                            columnNames.append(meta.getColumnName(i));
                            bindVariables.append('?');
                        
                    
                    String sql = "INSERT INTO " + table.substring(4) + " ("
                            + columnNames
                            + ") VALUES ("
                            + bindVariables
                            + ")";
                    statementHSQL = hsql.getDataSource().getConnection().prepareStatement(sql);
                    HashMap<String, Object> data = new HashMap<>();
                    int row = 0;
                    int lastId = 0;
                    String columnName;
                    while (rs.next()) 
                        for (int i = 1; i <= meta.getColumnCount(); i++) 
                            columnName = meta.getColumnName(i);
                            Object o = rs.getObject(i);
                            statementHSQL.setObject(i, o);
                            if (columnName.equals(mainColumn))
                                deleteIds.add(String.valueOf(o));
                            if (!(meta.getColumnType(i) == 2004)) data.put(columnName, o);
                            if (columnName.equals(mainColumn)) id = rs.getObject(i);
                        
                        int c = 1;
                        String update = MessageFormat.format("INSERT INTO 0M (1M, MIGRATED) VALUES(?, TRUE)",
                                table.substring(4), mainColumn).replace("\"M", "M\"");//migrated state is saved in other table
                        lastId = Integer.valueOf(String.valueOf(id));
                        statementUpd[row] = derby.getDataSource().getConnection().prepareStatement(update);
                        statementUpd[row].setObject(1, rs.getObject(mainColumn));
                        updateSQL = new StringBuilder();
                        statementHSQL.addBatch();
                        row += 1;
                    
                    /*
                     * Build delete query in case of inserted values in HSQLDB but not updated in DERBY
                     */
                    StringBuilder builder = new StringBuilder();
                    builder.append("(");
                    int count = 1;
                    for (String s : deleteIds) 
                        if (count > 1) builder.append(", ");
                        builder.append("?");
                        count++;
                    
                    builder.append(")");
                    String str = builder.toString();
                    String queryDelete = "DELETE FROM " + table.substring(4) + " WHERE " + mainColumn + " IN " + str;
                    PreparedStatement statementHSQLDel = hsql.getDataSource().getConnection().prepareStatement
                            (queryDelete);
                    int c = 1;
                    for (String s : deleteIds) 
                        statementHSQLDel.setObject(c, s);
                        c++;
                    
                    boolean deletes = statementHSQLDel.execute();
                    statementHSQLDel.close();
                    try 
                        DatabaseUtils.close(statementHSQLDel);
                     catch (Exception e) 
                        catchOutOfMemory(e);
                    
                    int[] result = statementHSQL.executeBatch();
                    StringBuilder resultS = new StringBuilder();
                    int stCounter = 0;
                    int stCounterInsert = 0;
                    int stCounterUpdate = 0;
                    String notarydebug;
                    for (int i : result) 
                        int upd = 0;
                        try 
                            if (i == 1) upd = statementUpd[stCounter].executeUpdate();
                         catch (Exception e) 
                            catchOutOfMemory(e);
                        
                        stCounterInsert += i;
                        stCounterUpdate += upd;
                        resultS.append(",").append(String.valueOf(i)).append("-").append(String.valueOf(upd));
                        stCounter += 1;
                    
                    statementHSQL.clearBatch();
                    try 
                        DatabaseUtils.close(statementHSQL);
                     catch (Exception e) 
                        catchOutOfMemory(e);
                    
                 catch (SQLException se) 
                    catchOutOfMemory(se);//otherstuff
                 catch (Exception e) 
                    catchOutOfMemory(e);
                
                try 
                    DatabaseUtils.close(rs);
                    DatabaseUtils.close(statement);
                 catch (Exception e) 
                    catchOutOfMemory(e);
                
                regs=regs-cant;
            
        
    catch (Exception e) 
        if (e.getMessage().equals("redo")) return true;//end the loop of regs maximun and get out of method.
    
return false;//end migration succesfully

private static int catchOutOfMemory(Throwable e) 
    if (e == null) return 0;
    if (e instanceof OutOfMemoryError) 
        Application.restartBat();
        return 1;
     else 
        return catchOutOfMemory(e.getCause());
    

编辑: 所以我按照评论中的建议进行更改以接受提交,如下所示:

Connection hsqlCon;
PrepareStatement hsqlStm;
hsqlCon = JdbcHSQLDB.getDataSource().getConnection();
hsqlStm = hsqlCon.prepareStatement(sql);
hsqlStm.addBatch();
hsqlStm.execute();
hsqlStm.close();
hsqlCon.close();

但我得到了相同的堆内存消耗:

【问题讨论】:

HSQLDB 使用的 URL 是什么?您是否使用内存中的 URL 而不是基于文件的 URL? bot DERBY 和 HSQLDB 是文件 我看到的唯一另一个问题是,当你使用它们时,你没有正确关闭连接和语句(尽管你的代码很难遵循它的结构),这会导致内存中的更多对象不必要的。 谢谢@Mark,如果语句保持打开状态,我会一一再次检查,我在某些地方使用了 null 并找到了一个。 你需要在多个事务中进行这种传输并提交每个事务。 HSQLDB 将未提交的行保存在内存中,如果数据库很大,可能会耗尽。 【参考方案1】:

HSQLDB 中的表类型在提供的代码中并不清楚。您必须对每个表使用此语句一次,以确保表数据存储在finename.data 文件中:

SET TABLE tableName TYPE CACHED

批量 INSERT 报告的顺序不正确。使用这个序列:

Connection hsqlCon;
PrepareStatement hsqlStm;
hsqlCon = JdbcHSQLDB.getDataSource().getConnection();
hsqlStm = hsqlCon.prepareStatement(sql);

 // repeat this block until all is finished
     // repeat for 1000 rows
        hsqlStm.addBatch();
     
    hsqlStm.executeBatch(); // after every 1000 rows
 

hsqlStm.close();
hsqlCon.close();

【讨论】:

以上是关于将 Big Derby 嵌入数据库迁移到 HSQLDB 抛出 java.lang.OutOfMemoryError: Java heap space的主要内容,如果未能解决你的问题,请参考以下文章

在运行时指定在何处创建或打开 Derby 嵌入式数据库

如何将数据从 Ms 访问迁移到 Derby 数据库或如何将数据从 My sql 迁移到 Derby 数据库

finedb(内置的HSQL数据库)迁移数据到MySQL

HSQL中的一个数据迁移问题,新数据库不包含几个表

SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase(代码片

SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase] 错误