1. 程式人生 > >mysql批量插入

mysql批量插入

對於一些資料量較大的系統,資料庫面臨的問題除了查詢效率低下,還有就是資料入庫時間長。特別像報表系統,每天花費在資料匯入上的時間可能會長達幾個小時或十幾個小時之久。因此,優化資料庫插入效能是很有意義的。

經過對MySQL innodb的一些效能測試,發現一些可以提高insert效率的方法,供大家參考參考。

1. 一條SQL語句插入多條資料。

常用的插入語句如:

  1. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  2.     VALUES ('0''userid_0''content_0', 0); 
  3. INSERTINTO
     `insert_table` (`datetime`, `uid`, `content`, `type`) 
  4.     VALUES ('1''userid_1''content_1', 1); 

修改成:

  1. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  2.     VALUES ('0''userid_0''content_0', 0), ('1''userid_1''content_1', 1); 

修改後的插入操作能夠提高程式的插入效率。這裡第二種SQL執行效率高的主要原因是合併後日志量(MySQL的binlog和innodb的事務讓 日誌)減少了,降低日誌刷盤的資料量和頻率,從而提高效率。通過合併SQL語句,同時也能減少SQL語句解析的次數,減少網路傳輸的IO。

這裡提供一些測試對比資料,分別是進行單條資料的匯入與轉化成一條SQL語句進行匯入,分別測試1百、1千、1萬條資料記錄。

2. 在事務中進行插入處理。

把插入修改成:

  1. START TRANSACTION
  2. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  3.     VALUES ('0''userid_0''content_0', 0); 
  4. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  5.     VALUES ('1''userid_1'
    'content_1', 1); 
  6. ... 
  7. COMMIT

使用事務可以提高資料的插入效率,這是因為進行一個INSERT操作時,MySQL內部會建立一個事務,在事務內才進行真正插入處理操作。通過使用事務可以減少建立事務的消耗,所有插入都在執行後才進行提交操作。

這裡也提供了測試對比,分別是不使用事務與使用事務在記錄數為1百、1千、1萬的情況。

3. 資料有序插入。

資料有序的插入是指插入記錄在主鍵上是有序排列,例如datetime是記錄的主鍵:

  1. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  2.     VALUES ('1''userid_1''content_1', 1); 
  3. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  4.     VALUES ('0''userid_0''content_0', 0); 
  5. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  6.     VALUES ('2''userid_2''content_2',2); 

修改成:

  1. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  2.     VALUES ('0''userid_0''content_0', 0); 
  3. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  4.     VALUES ('1''userid_1''content_1', 1); 
  5. INSERTINTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  6.     VALUES ('2''userid_2''content_2',2); 

由於資料庫插入時,需要維護索引資料,無序的記錄會增大維護索引的成本。我們可以參照innodb使用的B+tree索引,如果每次插入記錄都在索 引的最後面,索引的定位效率很高,並且對索引調整較小;如果插入的記錄在索引中間,需要B+tree進行分裂合併等處理,會消耗比較多計算資源,並且插入 記錄的索引定位效率會下降,資料量較大時會有頻繁的磁碟操作。

下面提供隨機資料與順序資料的效能對比,分別是記錄為1百、1千、1萬、10萬、100萬。

從測試結果來看,該優化方法的效能有所提高,但是提高並不是很明顯。

效能綜合測試:

這裡提供了同時使用上面三種方法進行INSERT效率優化的測試。

從測試結果可以看到,合併資料+事務的方法在較小資料量時,效能提高是很明顯的,資料量較大時(1千萬以上),效能會急劇下降,這是由於此時資料量 超過了innodb_buffer的容量,每次定位索引涉及較多的磁碟讀寫操作,效能下降較快。而使用合併資料+事務+有序資料的方式在資料量達到千萬級 以上表現依舊是良好,在資料量較大時,有序資料索引定位較為方便,不需要頻繁對磁碟進行讀寫操作,所以可以維持較高的效能。

注意事項:

1. SQL語句是有長度限制,在進行資料合併在同一SQL中務必不能超過SQL長度限制,通過max_allowed_packet配置可以修改,預設是1M,測試時修改為8M。

2. 事務需要控制大小,事務太大可能會影響執行的效率。MySQL有innodb_log_buffer_size配置項,超過這個值會把innodb的資料刷到磁碟中,這時,效率會有所下降。所以比較好的做法是,在資料達到這個這個值前進行事務提交。

yii專案中的使用

由於專案需要,要求是要單次往資料庫裡插入10000條資料,剛開始寫得程式碼如下:

$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放入事務中也會提升一點資料插入的效能!