MYSQL-JDBC批量新增-更新-刪除

1 概述

最近在極客時間買了幾個專欄,MYSQL實戰45講SQL必知必會,若是你想深刻MYSQL的話,推薦你看MYSQL實戰45講,很是不錯,而且必定要看留言區,留言區的質量很是高,丁奇老師太太太負責任了,我在極客時間買了很多課程,丁奇老師對大部分評論都進行了回答,這是在其餘專欄中不多見的,文章的內容+留言區的問題+丁奇老師的解答都很是不錯。這是目前爲止我在極客時間買到的最好的課程。mysql

固然若是你想入門SQL,你能夠看下SQL必知必會,該專欄比較簡單,屬於SQL入門課程。sql

隨着時代的變遷,我愈加以爲數據變得愈來愈重要,不管是大數據、仍是人工智能、物聯網,本質上都是數據在起做用。大數據是涉及從大量數據中收集,存儲,分析和獲取的通用平臺。人工智能和機器學習是兩種更智能更有效的方式篩選數據和信息的技術。移動和物聯網設備用於從客戶,用戶和受衆收集數據。數組

所以最近我一直在研究MYSQL和JDBC的高級用法,造成本篇博客,與你們一塊兒分享。關於MYSQL和JDBC的簡單用法和概述,你們能夠參考其餘博客,我就不重複造輪子了。服務器


2 開啓MYSQL服務端日誌

找到 my.ini 文件,個人電腦上是在 C:\ProgramData\MySQL\MySQL Server 5.7目錄下mybatis

而後重啓MYSQL服務器,在my.ini同級的Data目錄下,你就能夠看到 該日誌文件。機器學習


3 深刻MYSQL/JDBC批量插入

在url後面必定寫rewriteBatchedStatements=true,開啓批處理。學習

相似於:jdbc:mysql://127.0.0.1:3306/aaa?characterEncoding=UTF8&useUnicode=true&rewriteBatchedStatements=true

大數據

3.1 從一個例子出發

MYSQL 插入 就兩種形式this

  • insert into student('name') values('adai')
  • insert into student('name') values('adai'),('hello'),('sky')

對比一下插入效率,3000條數據,數據都是同樣的。

第一種:首先插入3000條數據,3000個insert,在navicat執行,耗時3.360s

而後在服務端看日誌,會發現mysql,是一條一條逐步insert的,總共服務端執行3000次insert

第二種:插入3000條數據,一個insert,在navicat執行,耗時0.241秒

而後看服務端日誌,會發現mysql,是批量插入的,只有一個insert,多個values

這應該很是容易理解,按照計算機理論知識,批量插入效率鐵定比單條插入效率高。

注意,若是批量插入中間出現錯誤,那麼整個insert會失敗,不會插入任何數據,及該條insert批量插入是一個事務操做,要麼所有插入成功,要麼所有都插入失敗


3.2 JDBC的批量插入操做

因爲SQL注入等問題,Statement已經用的不多了,JDBC咱們主要講preparedStatement的批量插入,核心代碼以下所示:

private static final String[] names = {"劉德華", "周杰倫", "張三丰", "諸葛亮", "司馬懿", "呆頭", "張學友", "愛德華", "火星", "太陽"};
    private static final Integer[] ages = {21, 31, 41, 51, 61, 71, 81, 91, 100, 101};
    private static final Integer[] heights = {170, 171, 181, 182, 190, 168, 173, 175, 199, 220};
    private static final Byte[] sexs = {0, 1};
    private static final String[] address = {"中國上海大連西路550號",
            "國北京市朝陽區大山子A東里小區23棟3單元7樓",
            "第五宇宙", "第七宇宙恆星所在處", "浪跡天涯", "太陽背面", "大海最低處", "四姑涼山", "秦嶺", "長城"};

    private static final Integer NUMBER = 100000;

private static void prepareStatementBatch(Connection connection) throws SQLException {
        String sql = "insert into  student(`username`,`age`,`height`,`sex`,`address`,`create_time`,`update_time`) values(?,?,?,?,?,now(),null)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        long begin = System.currentTimeMillis();
        for (int i = 0; i < NUMBER; i++) {
                String name = names[j] + i;
                int age = ages[j] + i;
                int height = heights[j] + i;
                Byte sex = sexs[0];
                String addre = address[j] + i;
                preparedStatement.setString(1, name);
                preparedStatement.setInt(2, age);
                preparedStatement.setInt(3, height);
                preparedStatement.setByte(4, sex);
                preparedStatement.setString(5, addre);
                preparedStatement.addBatch();
                if ((i + 1) % 500 == 0) {
                    preparedStatement.executeBatch();
                    preparedStatement.clearBatch();
                }
        }
        // 執行剩下的
        preparedStatement.executeBatch();
        long end = System.currentTimeMillis();
        System.out.println("prepareStatementBatch 消耗時間:" + (end - begin));
    }

打開mysql服務器日誌:咱們能夠看到就兩條 insert語句,後面跟了不少values...,從第一個例子能夠看出,這樣的執行效率很是高。


3.3 兩個常被忽略的問題

上面的代碼中出現了

if ((i + 1) % 500 == 0) {
      preparedStatement.executeBatch();
      preparedStatement.clearBatch();
}

我我的以爲有兩個緣由:

1. 防止內存溢出。
2. MYSQL有一個max_packet_allowed參數,會限制Server接受的數據包大小。有時候大的插入和更新會受 max_allowed_packet 參數限制,致使大數據寫入或者更新失敗。

可是通過個人代碼實驗,只會出現第一種內存溢出的狀況,而不會出現第二種max_packet_allowed超出的狀況。

由於MYSQL驅動在底層已經對max_packet_allowed進行了處理。 debug 源碼 進行跟蹤

而且在調用preparedStatment.executeBatch()方法後,不須要手動調用preparedStatement.clearBatch(),由於MYSQL驅動本身會在調用executeBatch()方法後,執行clearBatch()

protected long[] executeBatchInternal() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            ... ... ...                 

            } finally {
                this.statementExecuting.set(false);

                clearBatch(); // clearBatch()在 finally 語句塊中
            }
        }

總結一下,這裏有兩個常被忽略的問題:

  • MYSQL的prepareStatement.executeBatch()方法底層會自動判斷max_allowed_packet大小,而後對

    batch裏面的集合數據分批傳給MYSQL服務端,所以確定不會報

    com.mysql.jdbc.PacketTooBigException: Packet for query is too large (5372027 > 4194304)

    Mybatis的批量插入不會對max_allowed_packet進行判斷,所以當數據量大的時候,會報這個錯誤

  • preparedStatment.executeBatch()完後,會自動調用preparedStatement.clearBatch()方法,無需咱們手動再進行調用。

    3.4 Mybatis批量插入操做

兩種方式,數組、List。效果都是同樣的,這裏我用List進行演示,主要用到Mybatis中的<foreach>

標籤

<insert id="insert">
    insert into student
    (`username`,`age`,`height`,`sex`,`address`,`create_time`,`update_time`) values
    <foreach collection="studentList" item="student" index="index" separator=",">
       (#{student.username},#{student.age},#{student.height},#{student.sex},#{student.address},now(),null)   
     </foreach>

查看後臺MYSQL服務器日誌:

2019-10-31T14:35:50.817807Z   141 Query insert into student(`username`,`age`,`height`,`sex`,`address`,`create_time`,`update_time`) values
            ('劉德華0',21,170,0,'中國上海大連西路550號0',now(),null)
         , 
            ('周杰倫0',31,171,0,'國北京市朝陽區大山子A東里小區23棟3單元7樓0',now(),null)
         , 
            ('張三丰0',41,181,0,'第五宇宙0',now(),null)
         , 
            ('諸葛亮0',51,182,0,'第七宇宙恆星所在處0',now(),null)
         , 
            ('司馬懿0',61,190,0,'浪跡天涯0',now(),null)
         , 
            ('呆頭0',71,168,0,'太陽背面0',now(),null)
         , 
            ('張學友0',81,173,0,'大海最低處0',now(),null)
         , 
            ('愛德華0',91,175,0,'四姑涼山0',now(),null)
         , 
            ('火星0',100,199,0,'秦嶺0',now(),null)
         , 
            ('太陽0',101,220,0,'長城0',now(),null)
            ... ... ... ... ... ...
            ... ... ... ... ... ...
            ... ... ... ... ... ...

能夠看到,Mybatis底層就是使用一個insert,多個value的插入操做。

不過要特地留意,Mybatis的批量插入操做,不會像JDBC的preparedStatement.execute()同樣,會自動判斷MYSQL服務器的 max_allow_packet大小,而後進行分批傳輸。Mybatis會將全部的value拼接在一塊兒,而後將這整個insert語句傳給MYSQL服務器去執行。若是這整個sql語句超出了 max_allow_packet,那麼錯誤將會產生。

總結:

不論是MYSQL、JDBC、Mybatis批量插入,底層都是一個 insert、多個values組合。

3.5 誤區

不少人將批量插入效率很高的緣由,歸結於客戶端跟服務端交互變少了,由於客戶端一次會「攢」不少value,而後再發給服務端,這是不許確的,批量插入效率很高的緣由,主要是由於 insert ... value() ...value() ...value()這個SQL特性,這個sql特性省下的時間遠遠超過 客戶端和MYSQL服務端的交互所省下的時間。

4 MYSQL/JDBC批量更新


4.1 MYSQL不支持批量更新

MYSQL是不支持批量更新的

注意:這裏的批量更新指的是

update student set username = "adai" where id = 1;
update student set age = 22 where id = 3;
update student set address= '上海' where id = 6;
update student set username = ‘daitou’ and address= '上海' where id = 11;
... ... ... ... ... ...
... ... ... ... ... ...

相似於上面徹底不一樣的update語句,MYSQL服務端只用執行一次,就能所有更新。

可是咱們能夠利用一些sql技巧,來完成批量更新。可是也有很大的侷限性,例如要寫不少 CASE ... WHEN。

UPDATE table SET title = (CASE
WHEN id = 1 THEN ‘Great Expectations’
WHEN id = 2 THEN ‘War and Peace’
...
END)
WHERE id IN (1,2,...)

在實際開發中若是遇到大批量更新,通常作法是 事務+單條更新

START TRANSACTION;
UPDATE ...;
UPDATE ...;
UPDATE ...;
UPDATE ...;
COMMIT;


4.2 JDBC的批量更新

既然在MYSQL中是不支持批量更新的,那麼JDBC的 preparedStatement.addBatch()preparedStatement.executeBatch() 又是如何執行的呢?

通過代碼實驗:

當sql是update的時候

for (...){
    ...
    ...
    preparedStatement.addBatch()    
}
preparedStatement.executeBatch()
for(...){
    ...
    ...
    preparedStatement.executeUpdate() 
}

更新15000行數據:

沒有使用批量更新 12372

使用批量更新 12227

更新50000行數據:

沒有使用批量更新 41295

使用批量更新 39754

更新100000行數據:

沒有使用批量更新 80820

使用批量更新 78839

更新300000行數據:

沒有使用批量更新 241400

使用批量更新 230104


更新500000行數據:

沒有使用批量更新 410912

使用批量更新 398941

查看MYSQL服務器日誌:發現批量更新和單獨更新的日誌都是同樣的

update student set ..... where id = ..;
update student set ..... where id = ..;
update student set ..... where id = ..;
update student set ..... where id = ..;

能夠看出,使用批量更新,會比單獨更新快一些,這主要是由於客戶端和服務端交互次數變少,所省下的時間開銷。這也進一步證明了在 批量插入insert的時候,主要是insert ... value() ...value() ...value()這個sql特性大大的減小了時間花費。而不是像不少其餘博客說的是由於客戶端和服務端的交互次數減小。


4.3 注意一個小問題

在使用批量插入/更新的時候,若是已經將批量的sql傳給了MYSQL服務器,那麼即便中止了客戶端程序,這些sql也會被執行。


5 MYSQL/JDBC批量刪除

不管是MYSQL仍是JDBC,批量刪除和批量更新同樣。


6 總結

能夠看出 JDBC的 preparedStatment.addBatch()preparedStatment.executeBatch()用在批量增長insert時,可以極高的提升效率,可是用在 update 和 delete時,可以提高部分效率。可是遠遠沒有批量插入提高的多。

沒有特殊狀況限制,咱們在insert、update、delete的時候,建議開啓事務,而後執行完畢,手動commit。

SQL執行最快的方式以下:

connection.setAutoCommit(false);
for(int i = 0; i < NUMBER; i++){
    ...
    ...
    preparedStatement.addBatch(); //NUMBER值不能太大,不然會內存溢出。
}
preparedStatement.executeBatch();
connection.commit();
做者: 一杯熱咖啡AAA
出處: https://www.cnblogs.com/AdaiCoffee/ 本文以學習、研究和分享爲主,歡迎轉載。若是文中有不妥或者錯誤的地方還望指出,以避免誤人子弟。若是你有更好的想法和意見,能夠留言討論,謝謝!
相關文章
相關標籤/搜索