您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關MySQL JDBC Statement.executeBatch實踐問題是怎樣的,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
現在很少使用原生jdbc去實現代碼, 最近在測試MySQL批處理數據遇到一個問題: 執行Statement.executeBatch時效率極其低下(插入10000條數據), 斷點調試時在此處停頓很長時間(908712ms)
數據版本:
select version(); // 5.7.17-11-V2.0R530D002-20190816-1203-log
數據庫表:
CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_name` varchar(64) DEFAULT NULL, `age` smallint(6) DEFAULT NULL, `salary` decimal(10,2) DEFAULT NULL, `max_size` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
測試代碼如下:
public class BatchTest { public static void main(String[] args) throws SQLException, ClassNotFoundException { batch("com.mysql.cj.jdbc.Driver", "jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true", "root", "123456"); } private static void batch(String driver, String url, String username, String password) throws ClassNotFoundException, SQLException { // jdbc driver: mysql-connector-java:8.0.21 Class.forName(driver); Connection conn = DriverManager.getConnection(url, username, password); conn.setAutoCommit(false); PreparedStatement pstmt = conn.prepareStatement("select * from dual"); for(int i = 1; i <= 10000; i++){ String batchSql = "insert into t_user(user_name, age, salary, max_size) values (" + "'name" + i + "'," + i + "," + i + "," + i + ")"; pstmt.addBatch(batchSql); } long start = System.currentTimeMillis(); // 執行耗時很長 int[] rs = pstmt.executeBatch(); conn.commit(); // total ======> 908712 System.out.println("total ======> " + (System.currentTimeMillis() - start)); } }
原因
addBatch() 是在PreparedStatement定義的, addBatch(String) 是在Statement定義的,
MySQL JDBC驅動在默認情況下使用預編譯會無視executeBatch()語句, 把我們期望批量執行的一組sql語句拆散, 一條一條地發給MySQL數據庫, 直接造成較低的性能.
batchHasPlainStatements=true, 即使用PreparedStatement時使用了Statement.addBatch(String), 不執行批處理操作, 一條一條地發給MySQL數據庫, 直接造成較低的性能.(確使用方式: PreparedStatement + addBatch(), Statement + addBatch(sql))
JDBC驅動源碼:
public class ClientPreparedStatement extends com.mysql.cj.jdbc.StatementImpl implements JdbcPreparedStatement { /** * Does the batch (if any) contain "plain" statements added by * Statement.addBatch(String)? * * If so, we can't re-write it to use multi-value or multi-queries. */ protected boolean batchHasPlainStatements = false; // ...... protected long[] executeBatchInternal() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.connection.isReadOnly()) { throw new SQLException(Messages.getString("PreparedStatement.25") + Messages.getString("PreparedStatement.26"), MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT); } if (this.query.getBatchedArgs() == null || this.query.getBatchedArgs().size() == 0) { return new long[0]; } // we timeout the entire batch, not individual statements int batchTimeout = getTimeoutInMillis(); setTimeoutInMillis(0); resetCancelledState(); try { statementBegins(); clearWarnings(); // 1. batchHasPlainStatements 包含原始sql語句 // 2. rewriteBatchedStatements 使用批量 if (!this.batchHasPlainStatements && this.rewriteBatchedStatements.getValue()) { if (((PreparedQuery<?>) this.query).getParseInfo().canRewriteAsMultiValueInsertAtSqlLevel()) { return executeBatchedInserts(batchTimeout); } if (!this.batchHasPlainStatements && this.query.getBatchedArgs() != null && this.query.getBatchedArgs().size() > 3 /* cost of option setting rt-wise */) { return executePreparedBatchAsMultiStatement(batchTimeout); } } return executeBatchSerially(batchTimeout); } finally { this.query.getStatementExecuting().set(false); clearBatch(); } } } // ...... }
修正:
在連接url加上&rewriteBatchedStatements=true, 如: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
使用Statement.addBatch()替換Statement.addBatch(String)
public class BatchTest { public static void main(String[] args) throws SQLException, ClassNotFoundException { batch("com.mysql.cj.jdbc.Driver", "jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true", "root", "123456"); } private static void batch(String driver, String url, String username, String password) throws ClassNotFoundException, SQLException { Class.forName(driver); Connection conn = DriverManager.getConnection(url, username, password); conn.setAutoCommit(false); String pSql = "insert into t_user(user_name, age, salary, max_size) values (?, ?, ?, ?)"; PreparedStatement pstmt =conn.prepareStatement(pSql); for(int i = 1; i <= 10000; i++){ try { pstmt.setString(1, "name" + i); pstmt.setInt(2, i); pstmt.setInt(3, i); pstmt.setInt(4, i); pstmt.addBatch(); } catch (SQLException e) { e.printStackTrace(); } } long start = System.currentTimeMillis(); int[] rs = pstmt.executeBatch(); conn.commit(); // total ======> 2598 System.out.println("total ======> " + (System.currentTimeMillis() - start)); } }
關于MySQL JDBC Statement.executeBatch實踐問題是怎樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。