經(jīng)驗不在于年限,在于積累---專注互聯(lián)網(wǎng)軟件開發(fā)

          把工作當(dāng)事業(yè)做,把項目當(dāng)作品做!

            BlogJava :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
            55 Posts :: 0 Stories :: 66 Comments :: 0 Trackbacks
          在數(shù)據(jù)庫的相關(guān)開發(fā)中,經(jīng)常會遇到數(shù)據(jù)的批量插入問題。本文主要是通過實驗的方式探討批量數(shù)據(jù)插入的瓶頸,以及優(yōu)化建議。


          10w條記錄的插入作為實驗對象,采用下面的幾種方法插入:

          1.       普通插入:普通的一條條插入

          2.       普通插入+手動提交:setAutoCommit(false)、commit()

          3.       普通插入+手動提交+ prepareStatement方式

          4.       批量插入:addBatch、executeBatch

          5.       批量插入:insert into tableName (x,xx,xxx) values(x,xx,xxx),(xx,xxx,xxxx)…,

          6.       多線程插入。

          7.       InnoDB引擎和MyISAM引擎的比較。


          實驗環(huán)境:

          數(shù)據(jù)庫:MySQL 5.0

          機器硬件:

          內(nèi)存 3G 

          CPU AMD雙核4400+ 2.3G

          首先建立一個簡單的user表:

          CREATE TABLE `user` (                                         

                    `id` varchar(50) NOT NULL,                                  

                    `seqid` bigint(20) NOT NULL auto_increment,                 

                    `name` varchar(50) NOT NULL,                                 

                    PRIMARY KEY (`seqid`)

                  ) ENGINE=MyISAM DEFAULT CHARSET=utf8

          一、普通插入
          代碼:

           1Connection conn=source.getConnection();
           2Statement s=conn.createStatement();
           3String sql="";
           4long start=System.currentTimeMillis();
           5for(int i=0;i<100000;i++)
           6{
           7    sql="insert into user(id,name) value('"+(i+1)+"','chenxinhan')";
           8    s.execute(sql);
           9}

          10s.close();
          11conn.close();
          12long end=System.currentTimeMillis();
          13System.out.println("commonInsert()執(zhí)行時間為:"+(end-start)+"ms");
          輸出結(jié)果:
          commonInsert()執(zhí)行時間為:13828ms

          二、普通插入+手動提交:setAutoCommit(false)、commit()
          代碼:
           1Connection conn=source.getConnection();
           2conn.setAutoCommit(false);
           3Statement s=conn.createStatement();
           4String sql="";
           5long start=System.currentTimeMillis();
           6for(int i=0;i<100000;i++)
           7{
           8   sql="insert into user(id,name) value('"+(i+1)+"','chenxinhan')";
           9   s.execute(sql);
          10}

          11conn.commit();
          12s.close();
          13conn.close();
          14long end=System.currentTimeMillis();
          15System.out.println("commonInsert()執(zhí)行時間為:"+(end-start)+"ms");
          輸出結(jié)果:
          commonInsert()執(zhí)行時間為:13813ms
          對比分析:
          可以看出,僅僅是這種方式的設(shè)置,對性能的影響并不大。

          三、普通插入+手動提交+ prepareStatement方式
          代碼:
           1Connection conn=source.getConnection();
           2        conn.setAutoCommit(false);
           3        PreparedStatement ps=conn.prepareStatement("insert into user(id,name) value(?,?)");
           4        long start=System.currentTimeMillis();
           5        for(int i=0;i<100000;i++)
           6        {
           7            ps.setString(1,(i+1)+"");
           8            ps.setString(2,"chenxinhan");
           9            ps.execute();
          10        }

          11        conn.commit();
          12        ps.close();
          13        conn.close();
          14        long end=System.currentTimeMillis();
          15        System.out.println("prepareStatementInsert()執(zhí)行時間為:"+(end-start)+"ms");
          輸出結(jié)果:
          prepareStatementInsert()執(zhí)行時間為:12797ms
          對比分析:
          采用prepareStatement的方式確實可以提高一點性能,因為減少了數(shù)據(jù)庫引擎解析優(yōu)化SQL語句的時間,但是由于現(xiàn)在的插入語句太簡單,所以性能提升不明顯。

          四、批量插入:addBatch、executeBatch
          代碼:
           1        Connection conn=source.getConnection();
           2        conn.setAutoCommit(false);
           3        Statement s=conn.createStatement();
           4        String sql="";
           5        long start=System.currentTimeMillis();
           6        for(int j=0;j<100;j++)
           7        {
           8            for(int i=0;i<1000;i++)
           9            {
          10                sql="insert into user(id,name) value('"+(i+1)+"','chenxinhan')";
          11                s.addBatch(sql);
          12            }

          13            s.executeBatch();
          14            conn.commit();
          15            s.clearBatch();
          16        }

          17        s.close();
          18        conn.close();
          19        long end=System.currentTimeMillis();
          20        System.out.println("batchInsert()執(zhí)行時間為:"+(end-start)+"ms");
          輸出結(jié)果:
          batchInsert()執(zhí)行時間為:13625ms
          對比分析:
          按道理,這種批處理的方式是要快些的,但是測試結(jié)果卻不盡人意,有點不解,請高人拍磚。

          五、批量插入:insert into tableName (x,xx,xxx) values(x,xx,xxx),(xx,xxx,xxxx)…,
          代碼:
           1Connection conn=source.getConnection();
           2        conn.setAutoCommit(false);
           3        Statement s=conn.createStatement();
           4        StringBuilder sql=new StringBuilder("");
           5        long start=System.currentTimeMillis();
           6        for(int j=0;j<100;j++)
           7        {
           8            sql=new StringBuilder("");
           9            sql.append("insert into user(id,name) values ");
          10            for(int i=0;i<1000;i++)
          11            {
          12                if(i==999)
          13                    sql.append("('").append(i+1).append("',").append("'cxh')");
          14                else
          15                    sql.append("('").append(i+1).append("',").append("'cxh'),");
          16            }

          17            s.execute(sql.toString());
          18            conn.commit();
          19        }

          20        s.close();
          21        conn.close();
          22        long end=System.currentTimeMillis();
          23        System.out.println("manyInsert()執(zhí)行時間為:"+(end-start)+"ms");
          輸出結(jié)果:
          manyInsert()執(zhí)行時間為:937ms
          對比分析:
          發(fā)現(xiàn)采用這種方式的批量插入性能提升最明顯,有10倍以上的性能提升。所以這種方式是我推薦的批量插入方式!

          六、多線程插入
          在第五種方式的基礎(chǔ)上采用多線程插入。
          代碼:
           1final Connection conn=source.getConnection();
           2        for(int j=0;j<3;j++)
           3        {
           4            Thread t=new Thread(){
           5                @Override
           6                public void run() {
           7                    try
           8                    {
           9                        long start=System.currentTimeMillis();
          10                        Statement s=conn.createStatement();
          11                        StringBuilder sql=new StringBuilder("");
          12                        for(int j=0;j<100;j++)
          13                        {
          14                            conn.setAutoCommit(false);
          15                            sql=new StringBuilder("");
          16                            sql.append("insert into user (id,name) values ");
          17                            for(int i=0;i<1000;i++)
          18                            {
          19                                if(i==999)
          20                                    sql.append("('").append(i+1).append("',").append("'cxh')");
          21                                else
          22                                    sql.append("('").append(i+1).append("',").append("'cxh'),");
          23                            }

          24                            s.execute(sql.toString());
          25                            conn.commit();
          26                        }

          27                        s.close();
          28                        long end=System.currentTimeMillis();
          29                        System.out.println("multiThreadBatchInsert()執(zhí)行時間為:"+(end-start)+"ms");
          30                    }

          31                    catch(Exception e)
          32                    {
          33                        e.printStackTrace();
          34                    }

          35                }

          36            }
          ;
          37            t.start();
          38            //t.join();
          39        }
          輸出結(jié)果:
          multiThreadBatchInsert()執(zhí)行時間為:2437ms
          multiThreadBatchInsert()執(zhí)行時間為:2625ms
          multiThreadBatchInsert()執(zhí)行時間為:2703ms

          注意:上面我采用的是三個線程插入30w條數(shù)據(jù)。
          取最大時間為2703ms,較上面的937ms,基本還是三倍的時間。
          所以發(fā)現(xiàn)此時多線程也解決不了批量數(shù)據(jù)插入問題。原因就是,這時候的性能瓶頸不是CPU,而是數(shù)據(jù)庫!

          七、InnoDB引擎和MyISAM引擎的比較
          最后,分析一下,這兩個引擎對批量數(shù)據(jù)插入的影響。
          先建立user2數(shù)據(jù)表:

          CREATE TABLE `user2` (                                         

                    `id` varchar(50) NOT NULL,                                  

                    `seqid` bigint(20) NOT NULL auto_increment,                 

                    `name` varchar(50) NOT NULL,                                 

                    PRIMARY KEY (`seqid`)

                  ) ENGINE=InnoDB DEFAULT CHARSET=utf8

          代碼:
           1Connection conn=source.getConnection();
           2        conn.setAutoCommit(false);
           3        Statement s=conn.createStatement();
           4        StringBuilder sql=new StringBuilder("");
           5        long start=System.currentTimeMillis();
           6        for(int j=0;j<100;j++)
           7        {
           8            sql=new StringBuilder("");
           9            sql.append("insert into user2 (id,name) values ");
          10            for(int i=0;i<1000;i++)
          11            {
          12                if(i==999)
          13                    sql.append("('").append(i+1).append("',").append("'cxh')");
          14                else
          15                    sql.append("('").append(i+1).append("',").append("'cxh'),");
          16            }

          17            s.execute(sql.toString());
          18            conn.commit();
          19        }

          20        s.close();
          21        conn.close();
          22        long end=System.currentTimeMillis();
          23        System.out.println("manyInsert2()執(zhí)行時間為:"+(end-start)+"ms");
          輸出結(jié)果:
          manyInsert2()執(zhí)行時間為:3484ms
          注意:第七項的代碼和第五是一樣的,除了數(shù)據(jù)表名稱不同(user、user2)
          但是,
          InnoDB :3484ms
          MyISAM:937ms
          所以,MyISAM引擎對大數(shù)據(jù)量的插入性能較好。

          總結(jié):
          對于大數(shù)據(jù)量的插入,建議使用insert into tableName (x,xx,xxx) values(x,xx,xxx),(xx,xxx,xxxx)…,的方式,引擎建議使用MyISAM引擎。
          友情提醒:本博文章歡迎轉(zhuǎn)載,但請注明出處:陳新漢


          posted on 2009-09-01 12:57 hankchen 閱讀(8141) 評論(5)  編輯  收藏 所屬分類: SQL+MySQL

          Feedback

          # re: 數(shù)據(jù)庫批量數(shù)據(jù)插入問題分析 2009-09-01 13:30 hah
          多線程沒用,數(shù)據(jù)庫以此只能執(zhí)行一條命令,除非多個connection  回復(fù)  更多評論
            

          # re: 數(shù)據(jù)庫批量數(shù)據(jù)插入問題分析[未登錄] 2009-09-01 14:13 Bobby
          可惜的是MyISAM不支持事務(wù)處理  回復(fù)  更多評論
            

          # re: 數(shù)據(jù)庫批量數(shù)據(jù)插入問題分析 2009-09-01 19:55 vagrant
          你推薦的這種,語法是mysql特有的吧,有沒有其他數(shù)據(jù)庫的性能比較?  回復(fù)  更多評論
            

          # re: 數(shù)據(jù)庫批量數(shù)據(jù)插入問題分析 2009-09-01 20:38 陳新漢
          @hah
          其實,多個connection也沒用,因為瓶頸是數(shù)據(jù)庫的鎖機制。  回復(fù)  更多評論
            

          # re: 數(shù)據(jù)庫批量數(shù)據(jù)插入問題分析 2009-09-02 09:00 guest
          試試preparedstatement的batch insert?
            回復(fù)  更多評論
            


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 德保县| 阳信县| 常宁市| 五大连池市| 普兰店市| 清新县| 蒙阴县| 鄂尔多斯市| 永泰县| 长葛市| 栖霞市| 平安县| 厦门市| 平度市| 景泰县| 溧水县| 沂源县| 金秀| 怀安县| 冀州市| 广饶县| 新巴尔虎左旗| 东方市| 女性| 南涧| 云龙县| 武陟县| 岱山县| 阜新| 贺兰县| 汉寿县| 普洱| 云霄县| 沙雅县| 濉溪县| 仁寿县| 长阳| 伊金霍洛旗| 丰镇市| 辽中县| 宣城市|