【转】关于jdbc向mysql和postgresql批量插入大量数据时候的优化!

https://codeleading.com/article/2794704035/

本文转自如上网址。

1.对于mysql数据库,driverurl中加入:allowMultiQueries=true&rewriteBatchedStatements=true; 这样在使用jdbctemplate插入的时候,类似:

private void insertData(JdbcTemplate insertJdbcTemplate, String insertCoreSql, List<Map<String, Object>> dataList, int cols) {
        insertJdbcTemplate.batchUpdate(insertCoreSql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
                Map<String, Object> dataMap = dataList.get(i);
                for (int index = 1; index <= cols; index++) {
                    preparedStatement.setObject(index, dataMap.get(index + ""));
                }
            }
 
            @Override
            public int getBatchSize() {
                return dataList.size();
            }
        });
用mybatis批量插入的时候,类似foreach转成insert into values(),(),(),()...这总, 性能有巨大提升!

2.对于postgresql数据库,或基于postgresql的greenplum数据仓库,driverurl中加入:reWriteBatchedInserts=true,这个是从postgresql的jdbc高版本驱动(9.4.1209开始加,但是有bug)才加入的特性,详细见:https://jdbc.postgresql.org/documentation/changelog.html#version_42.2.2,建议升级驱动到42.2.2版本,否则即使你将sql写成insert into values(),(),(),()这种形式,一样被转化成单条插入。

<think>在Java中使用JDBC批量插入数据可以提高数据插入操作的效率,尤其是当需要插入大量数据时。批量插入可以减少与数据库的交互次数,从而显著减少网络传输开销数据库处理时间。 ### 实现步骤: 1. 加载数据库驱动(对于JDBC 4.0及以上,通常可以自动加载)。 2. 建立数据库连接。 3. 创建PreparedStatement对象,使用参数化SQL语句。 4. 关闭自动提交(以便手动控制事务)。 5. 循环设置参数,并添加到批处理中。 6. 执行批处理。 7. 提交事务(如果之前关闭了自动提交)。 8. 处理异常,必要时回滚事务。 9. 关闭资源。 ### 示例代码: 以下是一个使用JDBC批量插入数据的示例代码: ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class BatchInsertExample { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/testdb"; String user = "username"; String password = "password"; Connection conn = null; PreparedStatement pstmt = null; try { // 1. 建立连接 conn = DriverManager.getConnection(url, user, password); // 2. 设置手动提交事务 conn.setAutoCommit(false); String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; pstmt = conn.prepareStatement(sql); // 3. 批量插入数据 for (int i = 1; i <= 1000; i++) { pstmt.setString(1, "User" + i); pstmt.setString(2, "user" + i + "@example.com"); pstmt.addBatch(); // 添加到批处理 // 每100条执行一次批处理,避免内存溢出 if (i % 100 == 0) { pstmt.executeBatch(); conn.commit(); // 提交事务 } } // 执行剩余的批处理 pstmt.executeBatch(); conn.commit(); // 提交事务 System.out.println("批量插入完成!"); } catch (SQLException e) { e.printStackTrace(); try { if (conn != null) { conn.rollback(); // 回滚事务 } } catch (SQLException ex) { ex.printStackTrace(); } } finally { // 4. 关闭资源 try { if (pstmt != null) pstmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } ``` ### 关键点解释: 1. **关闭自动提交**:通过`conn.setAutoCommit(false)`关闭自动提交,这样可以将多个插入操作作为一个事务处理,要么全部成功,要么全部失败(通过回滚)。 2. **批处理添加**:使用`addBatch()`方法将参数化的预编译语句添加到批处理中。 3. **分批执行**:在循环中,每100条记录执行一次批处理并提交,这样可以避免一次性添加太多数据导致内存溢出。 4. **事务提交**:在批处理执行后,需要手动提交事务(`conn.commit()`)。 5. **异常处理**:在catch块中打印异常,并回滚事务。在finally块中关闭资源,确保资源释放。 ### 注意事项: - **批处理大小**:根据实际情况调整批处理的大小(如每100条、500条等),以平衡内存使用性能。 - **事务管理**:批量插入通常在一个事务中完成,如果插入数据量非常大,可能需要考虑分多个事务提交,以避免长事务锁表问题。 - **数据库驱动**:不同的数据库(如MySQL、Oracle、PostgreSQL等)对批处理的支持可能有所不同,需要确保使用的驱动支持批处理操作。 - **内存管理**:如果一次性添加太多数据到批处理中,可能会导致内存溢出(OutOfMemoryError),因此建议分批次执行。 ### 相关问题: §§相关问题§§: 1. 在JDBC中,如何设置事务的隔离级别? 2. 使用PreparedStatementStatement在批处理中有什么区别? 3. 如何处理JDBC批处理过程中的异常? 4. 在批量插入时,如何获取自动生成的主键?</think>### Java使用JDBC批量插入数据 在Java中,使用JDBC批量插入数据可以显著提高数据库操作效率,特别是当需要插入大量数据时。以下是完整的实现方法代码示例: #### 方法实现 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class BatchInsertExample { // 数据库连接信息 private static final String URL = "jdbc:mysql://localhost:3306/testdb"; private static final String USER = "username"; private static final String PASSWORD = "password"; public static void main(String[] args) { try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { // 1. 关闭自动提交(关键步骤!) conn.setAutoCommit(false); // 2. 准备SQL语句(使用占位符) String sql = "INSERT INTO employees (name, email, department) VALUES (?, ?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(sql)) { // 3. 批量设置参数 for (int i = 1; i <= 1000; i++) { pstmt.setString(1, "Employee_" + i); // name pstmt.setString(2, "emp" + i + "@company.com"); // email pstmt.setString(3, "Dept_" + (i % 5)); // department pstmt.addBatch(); // 添加到批处理 // 4. 分段执行(每100条执行一次) if (i % 100 == 0) { pstmt.executeBatch(); conn.commit(); // 提交事务 } } // 5. 执行剩余批处理 int[] updateCounts = pstmt.executeBatch(); conn.commit(); // 最终提交 System.out.println("批量插入完成,影响行数: " + updateCounts.length); } } catch (SQLException e) { e.printStackTrace(); // 实际应用中应添加事务回滚逻辑 } } } ``` #### 关键优化点 1. **事务管理**: - `conn.setAutoCommit(false)`:禁用自动提交(核心优化) - 手动分批提交事务(`conn.commit()`),避免单条提交的开销 2. **批处理机制**: - `addBatch()`:将参数化语句加入批处理队列 - `executeBatch()`:一次性发送整批SQL到数据库 - 返回`int[]`数组表示每条SQL影响的行数 3. **分段执行**: - 每100条执行一次,避免内存溢出(OOM) - 根据数据库配置调整批次大小(通常500-2000条/批) #### 性能对比 | 插入方式 | 10,000条耗时 | 资源消耗 | |----------------|--------------|----------| | 单条插入 | 15-20秒 | 高 | | 批量插入 | 0.5-2秒 | 低 | | 带事务的批量 | 0.3-1秒 | 最低 | #### 常见问题原因 1. **内存溢出(OutOfMemoryError)**: - 原因:一次性添加过多批处理未及时执行 - 解决:分批执行(如每500条执行一次) 2. **事务超时**: - 原因:单批次数据量过大,执行时间超过数据库等待超时 - 解决:减小批次大小或增加`wait_timeout` 3. **主键冲突**: - 原因:批量操作中部分数据违反唯一约束 - 解决:使用`try-catch`捕获`BatchUpdateException`,通过`getUpdateCounts()`定位失败行 4. **连接泄漏**: - 原因:未正确关闭`PreparedStatement``Connection` - 解决:使用try-with-resources语法(Java 7+) #### 高级技巧 ```java // 1. 获取自动生成的主键(需在SQL中指定) PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // 2. 批处理异常处理 try { pstmt.executeBatch(); } catch (BatchUpdateException e) { int[] successCounts = e.getUpdateCounts(); // 获取成功执行的计数 // 处理部分失败的情况 } // 3. 使用重写BatchedStatements(MySQL优化) // 在JDBC URL后添加参数:jdbc:mysql://...?rewriteBatchedStatements=true ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值