代码如下,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 是不是引用一直存在没有释放。
不知道你这个问题是否已经解决, 如果还没有解决的话:我可以通过以下方法来优化这个问题:
可以通过将插入数据分批次处理,每次处理一定数量的数据来避免 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();
}
通过调整数据库连接池的配置可以优化插入大量数据时的性能。具体来说,可以适当增加连接池中连接的数量,加快获取连接的速度。同时,可以调整连接的超时时间和心跳检测策略,确保连接的有效性。
示例代码:
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);
通过使用多线程并发插入可以加快插入大量数据的速度。可以将需要插入的数据分为多个子集,由不同的线程同时插入各自的子集,从而达到并发插入的效果。同时需要确保线程之间的安全性,避免数据冲突和死锁等问题。
示例代码:
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();
通过优化插入语句可以进一步提高插入大量数据的性能。具体来说,可以将多个插入操作合并为一个语句,使用批量操作来执行。同时,可以使用 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();