首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

JDBC 批量插入 MySQL 實戰:Statement.addBatch 提升百倍寫入性能

     編輯:MySQL 綜合教學

JDBC 批量插入 MySQL 實戰:Statement.addBatch 提升百倍寫入性能

單條 INSERT 頻繁交互數據庫是典型性能殺手。本文通過完整案例演示如何使用 JDBC Statement.addBatch() / executeBatch() 實現批量寫入,並解決 MySQL 下序列模擬、事務控制、異常處理等工程化細節。

場景需求

向 Emp 表批量插入 100 條記錄:

  • empno:由序列 emp_seq 自動生成
  • ename:"name" + 循環序號
  • sal:10000 以內隨機整數

核心方案:JDBC 批處理 API

  • 同一 Connection,一次網絡往返發送多條 SQL
  • 關閉自動提交 (setAutoCommit(false)),手動 commit()/rollback()
  • 數據量越大,批處理優勢越明顯 (減少網絡 RTT、解析優化開銷)

步驟 1:MySQL 模擬序列 (替代 Oracle SEQUENCE)

MySQL 無原生 SEQUENCE (8.0+ 引入),採用單行表 + LAST_INSERT_ID() 模擬:

CREATE TABLE emp_seq (seq BIGINT NOT NULL);
INSERT INTO emp_seq VALUES (0);

-- 獲取下一值 (原子操作,線程安全)
UPDATE emp_seq SET seq = LAST_INSERT_ID(seq + 1);
SELECT LAST_INSERT_ID();  -- 返回新值

關鍵點:LAST_INSERT_ID(expr) 將函數返回值設為 expr,並作為該連接的「最後插入 ID」,後續 SELECT LAST_INSERT_ID() 即可獲取,不受並發影響。

步驟 2:完整批量插入代碼 (Java)

public class BatchInsertDemo {
    public void batchAdd() {
        Connection con = null;
        Statement stmt = null;
        try {
            con = ConnectionSource.getConnection(); // 你的連接池獲取邏輯
            stmt = con.createStatement();
            con.setAutoCommit(false); // 關鍵:開啟事務

            for (int i = 0; i < 100; i++) {
                // 注意:MySQL 不支持 emp_seq.nextval,需先 UPDATE 再 SELECT,或在 SQL 中嵌套子查詢
                // 此處演示邏輯,實際建議使用 PreparedStatement 批處理 (見下文)
                String sql = "INSERT INTO emp(empno, ename, sal) VALUES (" +
                        "(UPDATE emp_seq SET seq = LAST_INSERT_ID(seq+1), LAST_INSERT_ID()), " + // 寫法需調整
                        "'name" + i + "', " +
                        new Random().nextInt(10000) + ")";
                stmt.addBatch(sql);
            }

            int[] results = stmt.executeBatch(); // 返回每條影響行數數組
            con.commit(); // 提交事務
            System.out.println("批量插入成功,共 " + results.length + " 條");
        } catch (SQLException e) {
            try { if (con != null) con.rollback(); } catch (SQLException ex) { ex.printStackTrace(); }
            throw new RuntimeException("批量插入失敗", e);
        } finally {
            closeQuietly(stmt);
            closeQuietly(con);
        }
    }
}

重要修正:MySQL 下 Statement 批處理的序列問題

上述代碼中 emp_seq.nextval 是 Oracle 語法,**MySQL 不支持**。實際工程中有兩種做法:

  1. 推薦:PreparedStatement 批處理 + 應用層生成 ID (如 Snowflake、UUID、Redis 原子增長),完全繞過數據庫序列。
  2. MySQL 8.0+ 使用原生 CREATE SEQUENCE;舊版本用存儲過程封裝 UPDATE ... LAST_INSERT_ID() 邏輯,Java 調用 {CALL next_val('emp_seq')}。

最佳實踐:PreparedStatement 批處理 (防注入、預編譯複用)

String sql = "INSERT INTO emp(empno, ename, sal) VALUES (?, ?, ?)";
try (Connection con = ds.getConnection();
     PreparedStatement ps = con.prepareStatement(sql)) {
    con.setAutoCommit(false);
    for (int i = 0; i < 100; i++) {
        long id = idGenerator.nextId(); // 應用層生成
        ps.setLong(1, id);
        ps.setString(2, "name" + i);
        ps.setInt(3, new Random().nextInt(10000));
        ps.addBatch();
    }
    ps.executeBatch();
    con.commit();
}

性能調優參數 (MySQL 連接串)

啟用 rewriteBatchedStatements=true (Connector/J 5.1.13+),驅動會將批量 INSERT 重寫為單條多值 INSERT INTO ... VALUES (...), (...), ...,極大減少網絡包與解析開銷:

jdbc:mysql://host:3306/db?rewriteBatchedStatements=true&useServerPrepStmts=true

總結

  • 批處理核心:addBatch() 緩存 → executeBatch() 一次性發送 → 事務提交。
  • 務必關閉自動提交,成批提交事務。
  • 生產環境**強制使用 PreparedStatement** 批處理,配合 rewriteBatchedStatements=true。
  • 大批次 (萬級以上) 建議分批提交 (如每 1000 條 commit),避免事務過大導致鎖爭用、Binlog 膨脹、回滾耗時。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved