mysql的批量插入会导致gc开销和/或java堆空间错误

q9rjltbz  于 2021-06-24  发布在  Mysql
关注(0)|答案(1)|浏览(532)

我试图一次插入大约190万行,作为mysql的批量插入。代码的工作方式就像一个咒语,行数不超过170万行,但我得到了gc开销和/或有时超过180万行的java堆空间错误。
代码:

try {
            // triples is of type ArrayList<String>
            String[] tripleToInsert= null;
            for (int i = 0; i < triples.size(); i++) {
                count++;
                tripleToInsert= triples.get(i).split("\\s+");

                /**Insert <s,p,o> into Triples table**/

                preparedStmt.setString(1, tripleToInsert[0].trim());
                preparedStmt.setString(2, tripleToInsert[1].trim());
                preparedStmt.setString(3, tripleToInsert[2].trim());

                preparedStmt.addBatch();
                preparedStmt.clearParameters();

                tripleToInsert=null;
            }

        }
        catch(OutOfMemoryError e)
        {
            System.out.println("OOM error: IN LOADING TO DB function on loop count: " + count);
            e.printStackTrace();
        }

String preEndTime= new SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().getTime());
        System.out.println("Preprocessing Ended:" + preEndTime);

        //Insert start time
        String startTime= new SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().getTime());
        System.out.println("Insert Started:" + startTime);

        // execute the prepared statement as a batch
        long[] results = preparedStmt.executeLargeBatch();

        System.out.println("Update Count size: "+ results.length);

        //Insert end time
        String endTime = new SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().getTime());
        System.out.println("Insert Completed:" + endTime);

遇到错误:

OOM error: IN LOADING TO DB function on loop count: 1888076
java.lang.OutOfMemoryError: GC overhead limit exceeded
    at com.mysql.jdbc.SingleByteCharsetConverter.toBytesWrapped(SingleByteCharsetConverter.java:230)
    at com.mysql.jdbc.StringUtils.getBytesWrapped(StringUtils.java:652)
    at com.mysql.jdbc.PreparedStatement.setString(PreparedStatement.java:4005)
    at LoadNTriplesByScript.loadTriplesByBatches(LoadNTriplesByScript.java:282)
    at LoadNTriplesByScript.insertNTriplesToDB(LoadNTriplesByScript.java:137)
    at LoadNTriplesByScript.main(LoadNTriplesByScript.java:77)

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.lang.Object.clone(Native Method)
    at java.util.TimeZone.clone(TimeZone.java:738)
    at sun.util.calendar.ZoneInfo.clone(ZoneInfo.java:647)
    at java.util.TimeZone.getDefault(TimeZone.java:625)
    at java.text.SimpleDateFormat.initializeCalendar(SimpleDateFormat.java:657)
    at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:601)
    at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:580)
    at LoadNTriplesByScript.loadTriplesByBatches(LoadNTriplesByScript.java:330)
    at LoadNTriplesByScript.insertNTriplesToDB(LoadNTriplesByScript.java:137)
    at LoadNTriplesByScript.main(LoadNTriplesByScript.java:77)

eclipse版本:oxygen.1a发行版(4.7.1a)
java堆大小:-xms2048m-xmx3072m
我不确定这是由字符串操作(split()方法)引起的,还是preparedstatement addbatch()方法对每个批的行数有限制。我设置了autocommit(false),然后在执行preparedstatement之后提交()。
注意:我读过很多关于gc开销和java堆错误的文章。任何关于为什么会发生这种情况的建议或建议都是值得赞赏的。

g6baxovj

g6baxovj1#

很有可能,你的内存用完了。该消息是误导性的,因为高gc开销通常是由于没有要收集的内容(所有内存都在使用中)而导致的。
批处理的原因是避免了每个命令的高开销。关于性能,使用1000行或1000000行的批处理几乎不重要。所以尝试小批量。
如果您的应用程序可以在一段时间内看到不完整的数据,也可以尝试较小的提交。
请注意,您在内存中保留了 triples 把一个分开 tripleToInsert . 对于1.8m行和3GB的总容量,每行使用的字节数可以少于1500字节。假设一个字符在java中是两个字节,假设您要存储它两次,而且总是有一些开销,因此它可能每行的字符数小于300个(只是猜测)。
你基本上做对了,只是这么大的一批需要很多内存。如果这只是一个导入,那么您可以通过在使用未拆分的三元组时清除它们来节省一些内存。
有时可以使用db提供的内置csv导入。

相关问题