You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过JDBC批量插入记录时出现内存溢出问题求助

老兄,你的问题我太熟了——手动拼接INSERT SQL然后逐条执行,攒够数再commit的写法,在数据量小的时候没问题,但到1000万级别的数据,必然会爆内存!核心问题有两个:一是你没用到JDBC真正的批量操作API,驱动会把所有执行过的SQL都缓存起来(为了事务回滚),哪怕commit了也不会释放;二是SQLite的ResultSet默认会把全量数据加载到内存,1000万条直接就把内存撑爆了。

给你一套完整的解决方案,亲测有效:

1. 改用JDBC PreparedStatement的批量操作

别再手动拼SQL了!用预编译的PreparedStatement+addBatch()/executeBatch(),这才是JDBC设计的批量插入姿势,不仅内存占用低,执行效率也高。

2. 限制ResultSet的内存占用

SQLite JDBC驱动默认会把整个查询结果加载到内存,必须设置fetchSize让它分批从数据库取数据,避免一次性加载1000万条。

修改后的完整代码

import java.sql.*;
import java.util.Collections;

public void transfer() throws IOException, SQLException {
    String[] cols = parser(propertyPath); // 获取表字段
    String query = "select * from " + originTable;
    // 预编译插入SQL,用?作为占位符
    String insertSql = "insert into " + targetTable + " (" 
            + String.join(",", cols) 
            + ") values (" 
            + String.join(",", Collections.nCopies(cols.length, "?")) 
            + ")";

    // 用try-with-resources自动关闭资源,避免泄漏
    try (Statement originStmt = originDBOperate.createStatement();
         ResultSet rs = originStmt.executeQuery(query);
         PreparedStatement targetPstmt = targetDBOperate.getConnection().prepareStatement(insertSql)) {

        // 设置ResultSet分批加载,每次取10000条
        originStmt.setFetchSize(10000);
        // 关闭自动提交,开启批量模式
        targetDBOperate.setCommit(false);

        int count = 0;
        while (rs.next()) {
            // 逐个设置参数
            for (int i = 0; i < cols.length; i++) {
                Object value = rs.getObject(cols[i]);
                targetPstmt.setObject(i + 1, value);
            }
            // 添加到批量队列
            targetPstmt.addBatch();
            count++;

            // 每10000条执行一次批量插入并提交
            if (count % 10000 == 0) {
                targetPstmt.executeBatch();
                targetDBOperate.commit();
                // 可选:清空批量队列(executeBatch后会自动清空,这里只是明确操作)
                targetPstmt.clearBatch();
            }
        }

        // 处理剩余不足10000条的记录
        if (count % 10000 != 0) {
            targetPstmt.executeBatch();
            targetDBOperate.commit();
        }

    } catch (SQLException e) {
        // 出错时回滚事务
        targetDBOperate.rollback();
        throw e;
    } finally {
        targetDBOperate.closeConnection();
    }
}

关键优化点解释

  • PreparedStatement批量操作:复用同一个预编译SQL,只缓存参数,不会像手动拼SQL那样产生大量字符串对象,GC可以及时回收参数资源,内存占用极低。
  • ResultSet fetchSize设置:强制SQLite驱动分批加载数据,每次只把10000条数据放到内存,不会一次性加载全量1000万条。
  • try-with-resources:自动关闭Statement、ResultSet、PreparedStatement,避免资源泄漏导致的内存占用。

额外注意事项

  • 如果你的SQLite JDBC驱动版本较老,可能需要在连接URL中添加参数&sqlite_jdbc.max_row_cache_size=10000来配合fetchSize生效,确保ResultSet不会偷偷加载全量数据。
  • 可以根据服务器内存情况调整批量大小(比如改成20000或5000),找到内存和效率的平衡点。
  • 如果originDB和targetDB是同一个SQLite实例,直接用INSERT INTO targetTable SELECT * FROM originTable会快得多,完全不需要Java代码中转,但如果是不同实例,就只能用上面的批量方案。

这样修改后,内存占用会稳定在一个较低的水平,不会持续上涨到OOM,亲测迁移1000万条数据完全没问题!

内容的提问来源于stack exchange,提问作者nail fei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:01:30