關於mysql上萬條資料同時插入時的效能優化
-
$code = new Code();
-
foreach ($codeModel as $v) {
-
$_code = clone $code;
-
$_code->rid = $rid;
-
$_code->created_at = time();
-
$_code->setAttributes($v);
-
$_code->save();
-
}
這段程式碼是將這10000條資料迴圈插入資料庫,效率是比較低,但還可以忍受,這裡插入的時間沒有測算,估計在10秒以內。這時候我手賤,搞了五萬條資料給同時插入,這時候問題來了,瀏覽器直接提示記憶體溢位(後來試了插入三萬條資料沒有提示溢位,但依然花了大概30秒時間)。有大神解釋是用了yii2的語法會導致多餘記憶體佔用,建議用原生sql語句,然後我把上面的程式碼改造下面的:
-
$db = Yii::$app->db;
-
foreach ($codeModel as $v) {
-
$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();
-
}
然後客戶端瀏覽器依然提示記憶體溢位(這時候插入三萬條資料的時候花了大概23秒時間,有進步,但還是不理想,所以繼續倒騰),所以只好在index.php里加上一句
ini_set('memory_limit','1024M');
將客戶端記憶體大小設定為1GB(不知道這樣表述正不正確,望指正),這時候插入五萬條資料的時候沒有提示記憶體溢位,但是執行速度還是很慢,五萬條資料30秒內都插不完,最後提示超時。
所以總結下來,將yii2語法改成了原生sql效能也只是提升一些,但也並不是想要的效果。後來在網上找了一些插入大量資料效能優化資料,提到了比較重要的一點是將
-
insert into tablename(f1,f2,...) values (d1,d2,...);
-
insert into tablename(f1,f2,...) values (d1,d2,...);
-
...
這樣的單條單條的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秒之內搞定。下面貼出部分參考程式碼
-
//下面是大於5000條資料拼接演算法,小於5000條就沒貼出來了
-
$chu = (int)($count/5000);//取整
-
$yu = $count%5000;//取餘
-
for ($i=0; $i < $chu; $i++) {
-
//每5000條資料組成一個insert語句,$codeModel是存放記錄的一個數組
-
$values = '';
-
for ($j=$i*5000; $j < ($i+1)*5000; $j++) {
-
//拼接values的值
-
$values .= '('.$codeModel[$j]['rid'].','.$codeModel[$j]['cid'].',"'.$codeModel[$j]['regcode'].'",0,'.$codeModel[$j]['status'].',0,'.time().'),';
-
}
-
$values = "insert into w_code (rid,cid,regcode,used_times,status,reason_id,created_at) values".substr($values,0,-1).';';
-
Yii::$app->db->createCommand($values)->execute();
-
}
另外,這些程式碼外層都放了事務回滾的!將多條insert放入事務中也會提升一點資料插入的效能!
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
另:
第一種方法:使用insert into 插入,程式碼如下:
1 2 3 4 5 6 7 |
|
最後顯示為: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 |
|
第三種方法:使用優化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 |
|
最後總結下,在插入大批量資料時,第一種方法無疑是最差勁的,而第二種方法在實際應用中就比較廣泛,第三種方法在插入測試資料或者其他低要求時比較合適