1. 程式人生 > >JAVA向Mysql插入億級別資料---測評

JAVA向Mysql插入億級別資料---測評

利用JAVA向Mysql插入一億數量級資料—效率測評

      前景:這幾天研究mysql優化中查詢效率時,發現測試的資料太少(10萬級別),利用 EXPLAIN 比較不同的 SQL 語句,不能夠得到比較有效的測評資料,大多模稜兩可,不敢通過這些資料下定論。

      所以通過隨機生成人的姓名、年齡、性別、電話、email、地址 ,向mysql資料庫大量插入資料,便於用大量的資料測試 SQL 語句優化效率。、在生成過程中發現使用不同的方法,效率天差萬別。

提示:

1. 先上Mysql資料庫,隨機生成的人員資料圖。分別是ID、姓名、性別、年齡、Email、電話、住址。下圖一共三千三百萬資料:

模擬資料

**
在資料量在億級別時。別點下面按鈕,會導致Navicat持續載入這億級別的資料,

導致電腦宕機。~覺著自己電腦配置不錯的可以去試試,可能會有驚喜

**

禁止點選

2、本次測評一共通過三種策略,五種情況,進行大批量資料插入測試

    策略分別是:
    1、Mybatis 輕量級框架插入(無事務)
    2、採用JDBC直接處理(開啟事務、無事務)
    3、採用JDBC批處理(開啟事務、無事務)

先展示測試結果:(耗費時間)

    Mybatis輕量級插入 -》 JDBC直接處理 -》 JDBC 批處理。

JDBC 批處理,效率最高

下面開始第一種策略測試:

2.1 Mybatis 輕量級框架插入(無事務)

    Mybatis是一個輕量級框架,它比hibernate輕便、效率高。但是處理大批

量的資料插入操作時,需要過程中實現一個ORM的轉換,本次測試存在例項,以及

未開啟事務,導致mybatis效率很一般。這裡實驗內容是:

    1、利用Spring框架生成mapper例項、建立人物例項物件

    2、迴圈更改該例項物件屬性、並插入。
//程式碼內無事務

    private long begin = 33112001;//起始id
    private long end = begin+100000;//每次迴圈插入的資料量
    private
String url = "jdbc:mysql://localhost:3306/bigdata?useServerPrepStmts=false&rewriteBatchedStatements=true&useUnicode=true&amp;characterEncoding=UTF-8"; private String user = "root"; private String password = "0203"; @org.junit.Test public void insertBigData2() { //載入Spring,以及得到PersonMapper例項物件。這裡建立的時間並不對最後結果產生很大的影響 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); PersonMapper pMapper = (PersonMapper) context.getBean("personMapper"); //建立一個人例項 Person person = new Person(); //計開始時間 long bTime = System.currentTimeMillis(); //開始迴圈,迴圈次數500W次。 for(int i=0;i<5000000;i++) { //為person賦值 person.setId(i); person.setName(RandomValue.getChineseName()); person.setSex(RandomValue.name_sex); person.setAge(RandomValue.getNum(1, 100)); person.setEmail(RandomValue.getEmail(4,15)); person.setTel(RandomValue.getTel()); person.setAddress(RandomValue.getRoad()); //執行插入語句 pMapper.insert(person); begin++; } //計結束時間 long eTime = System.currentTimeMillis(); System.out.println("插入500W條資料耗時:"+(eTime-bTime)); }
    本想測試插入五百萬條資料,但是實際執行過程中太慢,中途不得不終止程

序。最後得到52W資料,大約耗時兩首歌的時間(7~9分鐘)。隨後,利用mybatis向mysql插入 一萬 資料。結果如下: 

利用mybatis插入 一萬 條資料耗時:28613,即*28.6秒*

1W資料耗時

下面開始第二種策略測試:

2.2 採用JDBC直接處理(開啟事務、關閉事務)

    採用JDBC直接處理的策略,這裡的實驗內容分為開啟事務、未開啟事務是兩種,過程均如下:

    1、利用PreparedStatment預編譯

    2、迴圈,插入對應資料,並存入

事務對於插入資料有多大的影響呢?看下面的實驗結果:

//該程式碼為開啟事務
    private long begin = 33112001;//起始id
    private long end = begin+100000;//每次迴圈插入的資料量
    private String url = "jdbc:mysql://localhost:3306/bigdata?useServerPrepStmts=false&rewriteBatchedStatements=true&useUnicode=true&amp;characterEncoding=UTF-8";
    private String user = "root";
    private String password = "0203";


@org.junit.Test
    public void insertBigData3() {
        //定義連線、statement物件
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            //載入jdbc驅動
            Class.forName("com.mysql.jdbc.Driver");
            //連線mysql
            conn = DriverManager.getConnection(url, user, password);
             //將自動提交關閉
             conn.setAutoCommit(false);
            //編寫sql
            String sql = "INSERT INTO person VALUES (?,?,?,?,?,?,?)";
            //預編譯sql
            pstm = conn.prepareStatement(sql);
            //開始總計時
            long bTime1 = System.currentTimeMillis();

            //迴圈10次,每次一萬資料,一共10萬
            for(int i=0;i<10;i++) {
                //開啟分段計時,計1W資料耗時
                long bTime = System.currentTimeMillis();
                //開始迴圈
                while (begin < end) {
                    //賦值
                    pstm.setLong(1, begin);
                    pstm.setString(2, RandomValue.getChineseName());
                    pstm.setString(3, RandomValue.name_sex);
                    pstm.setInt(4, RandomValue.getNum(1, 100));
                    pstm.setString(5, RandomValue.getEmail(4, 15));
                    pstm.setString(6, RandomValue.getTel());
                    pstm.setString(7, RandomValue.getRoad());
                    //執行sql
                    pstm.execute();
                    begin++;
                }
                //提交事務
                conn.commit();
                //邊界值自增10W
                end += 10000;
                //關閉分段計時
                long eTime = System.currentTimeMillis();
                //輸出
                System.out.println("成功插入1W條資料耗時:"+(eTime-bTime));
            }
            //關閉總計時
            long eTime1 = System.currentTimeMillis();
            //輸出
            System.out.println("插入10W資料共耗時:"+(eTime1-bTime1));
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        }
    }
1、我們首先利用上述程式碼測試無事務狀態下,插入10W條資料需要耗時多少。如圖:

無事務插入

成功插入1W條資料耗時:21603
成功插入1W條資料耗時:20537
成功插入1W條資料耗時:20470
成功插入1W條資料耗時:21160
成功插入1W條資料耗時:23270
成功插入1W條資料耗時:21230
成功插入1W條資料耗時:20372
成功插入1W條資料耗時:22608
成功插入1W條資料耗時:20361
成功插入1W條資料耗時:20494
插入10W資料共耗時:212106

實驗結論如下:

在未開啟事務的情況下,平均每 21.2 秒插入 一萬 資料。

接著我們測試開啟事務後,插入十萬條資料耗時,如圖:

開始事務後

成功插入1W條資料耗時:4938
成功插入1W條資料耗時:3518
成功插入1W條資料耗時:3713
成功插入1W條資料耗時:3883
成功插入1W條資料耗時:3872
成功插入1W條資料耗時:3873
成功插入1W條資料耗時:3863
成功插入1W條資料耗時:3819
成功插入1W條資料耗時:3933
成功插入1W條資料耗時:3811
插入10W資料共耗時:39255

實驗結論如下:

開啟事務後,平均每 3.9 秒插入 一萬 資料

下面開始第三種策略測試:

2.3 採用JDBC批處理(開啟事務、無事務)

採用JDBC批處理時需要注意一下幾點:

1、在URL連線時需要開啟批處理、以及預編譯
String url = “jdbc:mysql://localhost:3306/User?rewriteBatched
-Statements=true&useServerPrepStmts=false”;

2、PreparedStatement預處理sql語句必須放在迴圈體外

程式碼如下:

    private long begin = 33112001;//起始id
    private long end = begin+100000;//每次迴圈插入的資料量
    private String url = "jdbc:mysql://localhost:3306/bigdata?useServerPrepStmts=false&rewriteBatchedStatements=true&useUnicode=true&amp;characterEncoding=UTF-8";
    private String user = "root";
    private String password = "0203";


    @org.junit.Test
    public void insertBigData() {
        //定義連線、statement物件
        Connection conn = null;
        PreparedStatement pstm = null;
        try {
            //載入jdbc驅動
            Class.forName("com.mysql.jdbc.Driver");
            //連線mysql
            conn = DriverManager.getConnection(url, user, password);
            //將自動提交關閉
            // conn.setAutoCommit(false);
            //編寫sql
            String sql = "INSERT INTO person VALUES (?,?,?,?,?,?,?)";
            //預編譯sql
            pstm = conn.prepareStatement(sql);
            //開始總計時
            long bTime1 = System.currentTimeMillis();

            //迴圈10次,每次十萬資料,一共1000萬
            for(int i=0;i<10;i++) {

                //開啟分段計時,計1W資料耗時
                long bTime = System.currentTimeMillis();
                //開始迴圈
                while (begin < end) {
                    //賦值
                    pstm.setLong(1, begin);
                    pstm.setString(2, RandomValue.getChineseName());
                    pstm.setString(3, RandomValue.name_sex);
                    pstm.setInt(4, RandomValue.getNum(1, 100));
                    pstm.setString(5, RandomValue.getEmail(4, 15));
                    pstm.setString(6, RandomValue.getTel());
                    pstm.setString(7, RandomValue.getRoad());
                    //新增到同一個批處理中
                    pstm.addBatch();
                    begin++;
                }
                //執行批處理
                pstm.executeBatch();
//                //提交事務
//                conn.commit();
                //邊界值自增10W
                end += 100000;
                //關閉分段計時
                long eTime = System.currentTimeMillis();
                //輸出
                System.out.println("成功插入10W條資料耗時:"+(eTime-bTime));
            }
            //關閉總計時
            long eTime1 = System.currentTimeMillis();
            //輸出
            System.out.println("插入100W資料共耗時:"+(eTime1-bTime1));
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        }
    }

首先開始測試
      無事務,每次迴圈插入10W條資料,迴圈10次,一共100W條資料。結果如下圖:

未開啟事務批處理

成功插入10W條資料耗時:3832
成功插入10W條資料耗時:1770
成功插入10W條資料耗時:2628
成功插入10W條資料耗時:2140
成功插入10W條資料耗時:2148
成功插入10W條資料耗時:1757
成功插入10W條資料耗時:1767
成功插入10W條資料耗時:1832
成功插入10W條資料耗時:1830
成功插入10W條資料耗時:2031
插入100W資料共耗時:21737

實驗結果:

使用JDBC批處理,未開啟事務下,平均每 2.1 秒插入 十萬 條資料

接著測試
      開啟事務,每次迴圈插入10W條資料,迴圈10次,一共100W條資料。結果如下圖:
這裡寫圖片描述

成功插入10W條資料耗時:3482
成功插入10W條資料耗時:1776
成功插入10W條資料耗時:1979
成功插入10W條資料耗時:1730
成功插入10W條資料耗時:1643
成功插入10W條資料耗時:1665
成功插入10W條資料耗時:1622
成功插入10W條資料耗時:1624
成功插入10W條資料耗時:1779
成功插入10W條資料耗時:1698
插入100W資料共耗時:19003

實驗結果:

使用JDBC批處理,開啟事務,平均每 1.9 秒插入 十萬 條資料

3 總結

        能夠看到,在開啟事務下 JDBC直接處理JDBC批處理 均耗時更短。

        Mybatis 輕量級框架插入 , mybatis在我這次實驗被黑的可慘了,哈哈。實際開啟事務以後,差距不會這麼大(差距10倍)。大家有興趣的可以接著去測試
        JDBC直接處理,在本次實驗,開啟事務和關閉事務,耗時差距5倍左右,並且這個倍數會隨著資料量的增大而增大。因為在未開啟事務時,更新10000條資料,就得訪問資料庫10000次。導致每次操作都需要操作一次資料庫。
        JDBC批處理,在本次實驗,開啟事務與關閉事務,耗時差距很微小(後面會增加測試,加大這個數值的差距)。但是能夠看到開啟事務以後,速度還是有提升。

         結論,設計到大量單條資料的插入,使用JDBC批處理和事務混合速度最快
         實測使用批處理+事務混合插入1億條資料耗時:174756毫秒

提示:

4、更多彩蛋

      JDBC批處理事務,開啟和關閉事務,測評插入20次,一次50W資料,一共一千萬資料耗時:
      {

            1、開啟事務(資料太長不全貼了)

             插入1000W資料共耗時:197654

>

            2、關閉事務(資料太長不全貼了)

            插入1000W資料共耗時:200540

       }

還是沒很大的差距~

借用

這裡寫圖片描述

分別是:

           不用批處理,不用事務;

           只用批處理,不用事務;

           只用事務,不用批處理;

           既用事務,也用批處理;(很明顯,這個最快,所以建議在處理大批量的資料時,同時使用批處理和事務)