请问jdbc PreparedStatement.executeBatch 报OOM?

代码如下,Mode 是一个枚举,区分在getZC方法中是从[一组读连接] 还是 [一组写连接]获取连接对象。简单执行下saveAll方法是正常的,
但是测试插入很多数据的情况下,比如 多个线程并行执行saveAll方法,每次插入一万条,循环不了多少次就OOM了,如果把内存设置很小,比如:
Xmx100M则循环几十次就OOM了。即使设置到几GB,也坚持不了几千次,设置这么大还OOM我觉得就奇怪了。OOM是ps.executeBatch();这行报的。请问这个怎么处理


public static <T> List<Object> saveAll(final Mode mode, final Class<T> cls, final String sqlParam, final List<T> tList) {
        final ZConnection zc = getZC(mode);
        final Connection connection = zc.getConnection();

        try {
            connection.setAutoCommit(false);

            final String sql = generateSaveAllSQL(cls, sqlParam);

            final PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            for (final T t : tList) {
                int i = 1;
                for (final Field field : cls.getDeclaredFields()) {
                    field.setAccessible(true);
                    final Object value = field.get(t);
                    ps.setObject(i, value);
                    i++;
                }
                ps.addBatch();
            }

            ps.executeBatch();

            final ResultSet rs = ps.getGeneratedKeys();
            final List<Object> r = new ArrayList<>();
            while (rs.next()) {
                final Object id = rs.getObject(1);
                r.add(id);
            }
            ps.clearBatch();

            return r;

        } catch (final Exception e) {
            e.printStackTrace();
            try {
                connection.rollback();
            } catch (final SQLException e1) {
                e1.printStackTrace();
            }

        } finally {
            try {
                connection.commit();
                instance.returnZConnection(zc);
            } catch (final SQLException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

你这个OOM 内存泄漏,看看传入方法的 tList 是不是引用一直存在没有释放。

不知道你这个问题是否已经解决, 如果还没有解决的话:
  • 以下回答来自chatgpt:

    我可以通过以下方法来优化这个问题:

    1. 使用分批次操作

    可以通过将插入数据分批次处理,每次处理一定数量的数据来避免 OutOfMemoryError 错误的发生。具体来说,可以将需要插入的数据按照一定的大小分为多个集合,在每次循环中只插入一个集合中的数据,直到所有的集合插入完成。

    示例代码:

    // 定义每次处理的数据量
    int batchSize = 10000;
    
    PreparedStatement ps = conn.prepareStatement(sql);
    for (int i = 0; i < data.size(); i += batchSize) {
        int end = Math.min(i + batchSize, data.size());
        List<Data> batchData = data.subList(i, end);
    
        // 处理batchData
        for (Data data : batchData) {
            ps.setString(1, data.getValue1());
            ps.setString(2, data.getValue2());
            // ...
            ps.addBatch();
        }
    
        // 执行批量操作
        ps.executeBatch();
    }
    
    1. 调整数据库连接池配置

    通过调整数据库连接池的配置可以优化插入大量数据时的性能。具体来说,可以适当增加连接池中连接的数量,加快获取连接的速度。同时,可以调整连接的超时时间和心跳检测策略,确保连接的有效性。

    示例代码:

    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost/test");
    config.setUsername("root");
    config.setPassword("password");
    config.setMaximumPoolSize(50);  // 增加连接池中连接的数量
    config.setConnectionTimeout(30000);  // 设置连接超时时间为30秒
    config.setConnectionTestQuery("SELECT 1");  // 设置心跳检测策略
    
    HikariDataSource dataSource = new HikariDataSource(config);
    
    1. 使用多线程并发插入

    通过使用多线程并发插入可以加快插入大量数据的速度。可以将需要插入的数据分为多个子集,由不同的线程同时插入各自的子集,从而达到并发插入的效果。同时需要确保线程之间的安全性,避免数据冲突和死锁等问题。

    示例代码:

    List<List<Data>> dataSubsets = splitDataIntoSubsets(data, numThreads);  // 将数据分为多个子集
    
    ExecutorService executor = Executors.newFixedThreadPool(numThreads);  // 创建线程池
    
    List<Future<Void>> futures = new ArrayList<>();
    for (List<Data> subset : dataSubsets) {
        Callable<Void> task = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                for (Data data : subset) {
                    // 插入数据
                }
                return null;
            }
        };
        futures.add(executor.submit(task));
    }
    
    for (Future<Void> future : futures) {
        future.get();  // 等待所有子线程执行完成
    }
    
    executor.shutdown();
    
    1. 优化插入语句

    通过优化插入语句可以进一步提高插入大量数据的性能。具体来说,可以将多个插入操作合并为一个语句,使用批量操作来执行。同时,可以使用 ON DUPLICATE KEY UPDATE 语句来避免插入重复的数据。

    示例代码:

    String sql = "INSERT INTO table_name (col1, col2, col3) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2), col3 = VALUES(col3)";
    
    PreparedStatement ps = conn.prepareStatement(sql);
    for (Data data : dataList) {
        ps.setString(1, data.getValue1());
        ps.setString(2, data.getValue2());
        ps.setString(3, data.getValue3());
        ps.addBatch();
    }
    ps.executeBatch();
    

如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^