我們都知道在MySQL中批量insert的速度會比一條條insert快很多,在MySQL中批量更新我們可能使用update,replace into來操作,下面小編來給各位同學詳細介紹MySQL 批量更新與性能吧。
批量更新
MySQL更新語句很簡單,更新一條數據的某個字段,一般這樣寫:
UPDATE table SET field = 'value' WHERE other_field = 'other_value';
如果更新同一字段為同一個值,MySQL也很簡單,修改下WHERE即可:
UPDATE table SET field='value' WHERE other_field in (val_1, val_2, ...);
那如果更新多條數據為不同的值,可能很多人會這樣寫:
即是循環一條一條的更新記錄,一條記錄update一次,這樣性能很差,也很容易造成阻塞。
那么能不能一條sql語句實現批量更新呢?MySQL並沒有提供直接的方法來實現批量更新,但是可以用點小技巧來實現。
UPDATE table SET field = CASE id WHEN 1 THEN 'value' WHEN 2 THEN 'value' WHEN 3 THEN 'value' END WHERE id IN (1,2,3)
這里使用了 CASE WHEN 這個小技巧來實現批量更新。
舉個例子:
UPDATE categories SET display_order = CASE id WHEN 1 THEN 3 WHEN 2 THEN 4 WHEN 3 THEN 5 END WHERE id IN (1,2,3)
這句SQL的意思是,更新display_order 字段,如果id=1 則display_order 的值為3,如果id=2 則 display_order 的值為4,如果id=3 則 display_order 的值為5。
即是將條件語句寫在了一起。
這里的WHERE部分不影響代碼的執行,但是會提高sql執行的效率。
確保sql語句僅執行需要修改的行數,這里只有3條數據進行更新,而WHERE子句確保只有3行數據執行。
如果更新多個值的話,只需要稍加修改:
到這里,已經完成一條MySQL語句更新多條記錄了。
但是要在業務中運用,需要結合服務端語言,這里以php為例,構造這條MySQL語句:
$display_order = array( 1 => 4, 2 => 1, 3 => 2, 4 => 3, 5 => 9, 6 => 5, 7 => 8, 8 => 9 ); $ids = implode(',', array_keys($display_order)); $sql = "UPDATE categories SET display_order = CASE id "; foreach ($display_order as $id => $ordinal) { $sql .= sprintf("WHEN %d THEN %d ", $id, $ordinal); } $sql .= "END WHERE id IN ($ids)"; echo $sql;
這個例子,有8條記錄進行更新,代碼也很容易理解。
性能分析
當我使用上萬條記錄利用MySQL批量更新,發現使用最原始的批量update發現性能很差。
將網上看到的總結一下一共有以下三種辦法:
1、批量update,一條記錄update一次,性能很差
2、replace into 或者insert into ...on duplicate key update
replace into test_tbl (id,dr) values (1,'2'),(2,'3'),...(x,'y'); 或者使用 insert into test_tbl (id,dr) values (1,'2'),(2,'3'),...(x,'y') on duplicate key update dr=values(dr);
3、創建臨時表,先更新臨時表,然后從臨時表中update
CREATE TEMPORARY TABLE tmp ( id int(4) primary key, dr varchar(50) ); INSERT INTO tmp VALUES (0,'gone'), (1,'xx'),...(m,'yy'); UPDATE test_tbl, tmp SET test_tbl.dr=tmp.dr WHERE test_tbl.id=tmp.id;
注意:這種方法需要用戶有temporary 表的create 權限。
下面是上述方法update 100000條數據的性能測試結果:
1、逐條UPDATE
2、REPLACE INTO
3、INSERT INTO ON DUPLICATE KEY UPDATE
4、CREATE TEMPLORARY TABLE 並 UPDATE
就測試結果來看,測試當時使用replace into性能較好。
注意:
REPLACE INTO 和 INSERT INTO ON DUPLICATE KEY UPDATE 的不同在於:
REPLACE INTO操作本質是對重復的記錄先DELETE 后INSERT,如果更新的字段不全會將缺失的字段置為缺省值。
INSERT INTO則只是UPDATE重復記錄,不會改變其它字段。
//實測臨時表更新mysql的性能數據如下。
一條條更新執行結果,22079條記錄
批量sql更新執行結果,性能有所提升但是並不高
臨時表更新,提升的幅度很多
附上執行邏輯的代碼
$strartTime=microtime(true); $optArr=array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES UTF8'); $dsn='mysql:host=172.12.10.155; port=3306; dbname=kana_task'; $pdoObj=new PDO($dsn, 'username', 'password', $optArr); $sql = "SELECT * FROM `kana_task`.`cdkey_list` "; echo $sql; $ret = $pdoObj->query($sql); if($ret instanceof PDOStatement) { while($row=$ret->fetch(PDO::FETCH_ASSOC)){ echo $row['id'].PHP_EOL; $allInfo[$row['id']]=$row; } } $pdoObj->exec('create table cdkey_list_tmp like cdkey_list;'); $baseStr='INSERT INTO `kana_task`.`cdkey_list_tmp` (id,overtime,`name`,`pid`,`content`,`aid`,`tid`,`type`) VALUES ' ; $i=$tnum=0; $sql=''; echo '查詢耗時:'.(microtime(true)-$strartTime).PHP_EOL; //循環更新時間 foreach ($allInfo as $id=>$tmpVal){ $tmpVal['overtime']=$tmpVal['overtime']+1; $lineStr="({$tmpVal['id']},{$tmpVal['overtime']},'{$tmpVal['name']}',{$tmpVal['pid']},'{$tmpVal['content']}',{$tmpVal['aid']},{$tmpVal['tid']},{$tmpVal['type']}),"; $sql=$sql.$lineStr; $i++; $tnum++; if($i>=500){//每次插入500條 $sql=substr($sql,0,-1); $sql=$baseStr.$sql.";"; $pdoObj->exec($sql); echo $tnum.PHP_EOL; $i=0; $sql=''; } //if($tnum>15) break; } echo '插入完成耗時:'.(microtime(true)-$strartTime).PHP_EOL; $pdoObj->exec('UPDATE `kana_task`.`cdkey_list`,`kana_task`.`cdkey_list_tmp` SET cdkey_list.overtime=cdkey_list_tmp.overtime WHERE cdkey_list.id=cdkey_list_tmp.id;'); echo '更新完成耗時:'.(microtime(true)-$strartTime).PHP_EOL; //清空臨時表 $pdoObj->exec('truncate table `kana_task`.`cdkey_list_tmp`;'); echo '總耗時:'.(microtime(true)-$strartTime).PHP_EOL; var_dump(count($allInfo));exit; exit;
使用Replace into 方式更新的測試結果
此方法和臨時表的性能差不多,但是操作更加簡單,SQL邏輯也簡單,
但是存在字段變更或者其它操作導致的其它非必要更新字段值被誤寫的風險
如果是千萬級別的數據表。建議是分批次更新。后續更好的優化方式歡迎大家拍磚。