1. 程式人生 > >關於mysql上萬條資料同時插入時的效能優化

關於mysql上萬條資料同時插入時的效能優化

 
  1. $code = new Code();

  2. foreach ($codeModel as $v) {

  3. $_code = clone $code;

  4. $_code->rid = $rid;

  5. $_code->created_at = time();

  6. $_code->setAttributes($v);

  7. $_code->save();

  8. }

這段程式碼是將這10000條資料迴圈插入資料庫,效率是比較低,但還可以忍受,這裡插入的時間沒有測算,估計在10秒以內。這時候我手賤,搞了五萬條資料給同時插入,這時候問題來了,瀏覽器直接提示記憶體溢位(後來試了插入三萬條資料沒有提示溢位,但依然花了大概30秒時間)。有大神解釋是用了yii2的語法會導致多餘記憶體佔用,建議用原生sql語句,然後我把上面的程式碼改造下面的:

 
  1. $db = Yii::$app->db;

  2. foreach ($codeModel as $v) {

  3. $db->createCommand('insert into w_code (rid,cid,regcode,used_times,status,reason_id,created_at) values (:rid,:cid,:regcode,:used_times,:status,:reason_id,:created_at)', [':rid'=>$rid,':cid'=>$v['cid'],':regcode'=>$v['regcode'],':used_times'=>0,':status'=>$v['status'],':reason_id'=>0,':created_at'=>time()])->execute();

  4. }

然後客戶端瀏覽器依然提示記憶體溢位(這時候插入三萬條資料的時候花了大概23秒時間,有進步,但還是不理想,所以繼續倒騰),所以只好在index.php里加上一句

ini_set('memory_limit','1024M');

將客戶端記憶體大小設定為1GB(不知道這樣表述正不正確,望指正),這時候插入五萬條資料的時候沒有提示記憶體溢位,但是執行速度還是很慢,五萬條資料30秒內都插不完,最後提示超時。
所以總結下來,將yii2語法改成了原生sql效能也只是提升一些,但也並不是想要的效果。後來在網上找了一些插入大量資料效能優化資料,提到了比較重要的一點是將

 
  1. insert into tablename(f1,f2,...) values (d1,d2,...);

  2. insert into tablename(f1,f2,...) values (d1,d2,...);

  3. ...

這樣的單條單條的insert語句改造成

insert into tablename(f1,f2,...) values (d1,d2,...),(d1,d2,...),(d1,d2,...);

這種一次insert多條記錄,效能會提升比較明顯,所以我就開始試驗這種方法,將每條記錄在程式碼裡迴圈拼接成一條原生insert語句再進行插入(想想感覺可行性很高),拼接完成後依然繼續插入五萬條資料,拼接出來的sql語句就成了

insert into tablename(f1,f2,...) values (d1,d2,...),(d1,d2,...),(d1,d2,...)...;//此處省略了49997條記錄

瀏覽器執行插入資料的頁面,bong...,提示Mysql server has gone away!,mysql崩潰了。蛋疼~!然後尋思著將這五萬條資料分批次進行插入,這樣就不會產生資料庫崩潰的情況,所以我將這五萬條資料按照五千個一組分批插入,最後再執行這個頁面,bong...五萬條資料兩秒之內就給全部插入進去了,兩秒。。(這裡已經去掉了前面加上的ini_set('memory_limit','1024M');)效率跟之前比提高了幾十倍,瞬間感覺整個人都變好了。又試了再插入三萬條資料,1秒之內搞定。下面貼出部分參考程式碼

 
  1. //下面是大於5000條資料拼接演算法,小於5000條就沒貼出來了

  2. $chu = (int)($count/5000);//取整

  3. $yu = $count%5000;//取餘

  4. for ($i=0; $i < $chu; $i++) {

  5. //每5000條資料組成一個insert語句,$codeModel是存放記錄的一個數組

  6. $values = '';

  7. for ($j=$i*5000; $j < ($i+1)*5000; $j++) {

  8. //拼接values的值

  9. $values .= '('.$codeModel[$j]['rid'].','.$codeModel[$j]['cid'].',"'.$codeModel[$j]['regcode'].'",0,'.$codeModel[$j]['status'].',0,'.time().'),';

  10. }

  11. $values = "insert into w_code (rid,cid,regcode,used_times,status,reason_id,created_at) values".substr($values,0,-1).';';

  12. Yii::$app->db->createCommand($values)->execute();

  13. }

另外,這些程式碼外層都放了事務回滾的!將多條insert放入事務中也會提升一點資料插入的效能!

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

另:

 

第一種方法:使用insert into 插入,程式碼如下:

?

1

2

3

4

5

6

7

$params array(‘value'=>'50′);

set_time_limit(0);

echo date(“H:i:s”);

for($i=0;$i<2000000;$i++){

$connect_mysql->insert($params);

};

echo date(“H:i:s”);

最後顯示為:23:25:05 01:32:05 也就是花了2個小時多!

第二種方法:使用事務提交,批量插入資料庫(每隔10W條提交下)最後顯示消耗的時間為:22:56:13 23:04:00 ,一共8分13秒  ,程式碼如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

echo date(“H:i:s”);

$connect_mysql->query(‘BEGIN');

$params array(‘value'=>'50′);

for($i=0;$i<2000000;$i++){

$connect_mysql->insert($params);

if($i%100000==0){

$connect_mysql->query(‘COMMIT');

$connect_mysql->query(‘BEGIN');

}

}

$connect_mysql->query(‘COMMIT');

echo date(“H:i:s”);

第三種方法:使用優化SQL語句:將SQL語句進行拼接,使用 insert into table () values  (),(),(),()然後再一次性插入,如果字串太長,

則需要配置下MYSQL,在mysql 命令列中執行 :set global max_allowed_packet =  2*1024*1024*10;消耗時間為:11:24:06 11:25:06;

插入200W條測試資料僅僅用了1分鐘!程式碼如下:

?

1

2

3

4

5

6

$sql= “insert into twenty_million (value) values”;

for($i=0;$i<2000000;$i++){

$sql.=”('50′),”;

};

$sql substr($sql,0,strlen($sql)-1);

$connect_mysql->query($sql);

最後總結下,在插入大批量資料時,第一種方法無疑是最差勁的,而第二種方法在實際應用中就比較廣泛,第三種方法在插入測試資料或者其他低要求時比較合適