【问题标题】:out of memory when insert record batch through jdbc通过 jdbc 插入记录批处理时内存不足
【发布时间】:2018-06-01 09:07:29
【问题描述】:

我想将 originDB(sqlite3) 中的表(1000 万条记录)复制到另一个名为 targetDB 的数据库中。
我的方法的过程是: 从源表读取数据并生成ResultSet,然后为每条记录生成对应的插入sql,当记录数达到10000时执行commit批量插入。
代码如下:

public void transfer() throws IOException, SQLException {
    targetDBOperate.setCommit(false);//batch insert
    int count = 0;
    String[] cols = parser(propertyPath);//get fields of data table
    String query = "select * from " + originTable;
    ResultSet rs = originDBOperate.executeQuery(query);//get origin table
    String base = "insert into " + targetTable;
    while(rs.next()) {
        count++;
        String insertSql = buildInsertSql(base,rs,cols);//corresponding insert sql
        targetDBOperate.executeSql(insertSql);
        if(count%10000==0) {
            targetDBOperate.commit();// batch insert
        }
    }
    targetDBOperate.closeConnection();
}

下图是内存使用趋势,纵轴代表内存使用情况

我们可以说它会越来越大,直到内存不足。 stackoverflow有一些相关的问题,例如Out of memory when inserting records in SQLite, FireDac, Delphi ,但是我没有解决我的问题,因为我们使用了不同的实现方法。
我的假设是当记录数没有达到10000时,这些相应的插入sql将被缓存在内存中并且默认执行提交时它们没有被删除?每一个建议都将不胜感激。

【问题讨论】:

  • 一些建议:1) 确保将 autoCommit 设置为 false。 2)使用真正的批量更新addBatch,最重要的是确保你在insert语句中使用绑定变量(这可能是你的内存问题的原因)。

标签: java jdbc sqlite


【解决方案1】:

通过在 SQLite 或任何其他关系数据库中移动更多的行,您应该遵循一些基本原则:

1) 将autoCommit 设置为false,即不提交每个插入

2) 使用批量更新,即不要为每一行往返

3) 使用prepared statement,即不解析每个插入。

将这些放在一起,您会得到以下代码:

cn 是源连接,cn2 是目标连接。

对于每个插入的行,您调用addBatch,但每个batchSize 只调用一次executeBatch,这会启动往返。

不要忘记循环末尾的最后一个executeBatch 和最后一个commit

cn2.setAutoCommit(false)

String SEL_STMT = "select id, col1,col2 from tab1"
String INS_STMT = "insert into tab2(id, col1,col2) values(?,?,?)"

def batchSize = 10000


def stmt = cn.prepareStatement(SEL_STMT)
def stmtIns = cn2.prepareStatement(INS_STMT) 

rs = stmt.executeQuery()

while(rs.next())
  {
    stmtIns.setLong(1,rs.getLong(1))
    stmtIns.setString(2,rs.getString(2))
    stmtIns.setTimestamp(3,rs.getTimestamp(3))
    stmtIns.addBatch();
    i += 1

    if (i == batchSize) {
        def insRec = stmtIns.executeBatch();
        i = 0
        }

  }
rs.close()
stmt.close()

def insRec = stmtIns.executeBatch();

stmtIns.close()
cn2.commit()

使用 sqlite-jdbc-3.23.1 以您的大小进行样本测试:

inserted rows: 10000000
total time taken to insert the batch = 46848 ms

我没有观察到任何内存问题或大型事务的问题

【讨论】:

    【解决方案2】:

    您正尝试通过执行以下操作一次性获取 10M 条记录。这肯定会像任何东西一样吞噬你的记忆

    String query = "select * from " + originTable;
    ResultSet rs = originDBOperate.executeQuery(query);//get origin table
    

    使用分页查询来读取批次并进行批次更新。

    您甚至没有进行批量更新您只是通过执行以下代码一个接一个地触发 10K 查询

    String insertSql = buildInsertSql(base,rs,cols);//corresponding insert sql
        targetDBOperate.executeSql(insertSql);
        if(count%10000==0) {
            targetDBOperate.commit();// This simply means that you are commiting after 10K records
        }
    

    【讨论】:

    • 结果集不一定会同时实现所有行。 sqlite 不支持获取大小设置吗?
    • 执行commit方法后会释放10k插入sql吗?
    猜你喜欢
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多