MySQL中的多个更新

我知道你可以一次插入多行,有没有办法一次更新多行(如在一个查询中)在MySQL中?

编辑:例如,我有以下

Name id Col1 Col2 Row1 1 6 1 Row2 2 2 3 Row3 3 9 5 Row4 4 16 8 

我想将所有以下更新结合到一个查询中

 UPDATE table SET Col1 = 1 WHERE id = 1; UPDATE table SET Col1 = 2 WHERE id = 2; UPDATE table SET Col2 = 3 WHERE id = 3; UPDATE table SET Col1 = 10 WHERE id = 4; UPDATE table SET Col2 = 12 WHERE id = 4; 

是的,这是可能的 – 你可以使用INSERT … ON DUPLICATE KEY UPDATE。

用你的例子:

 INSERT INTO table (id,Col1,Col2) VALUES (1,1,1),(2,2,3),(3,9,3),(4,10,12) ON DUPLICATE KEY UPDATE Col1=VALUES(Col1),Col2=VALUES(Col2); 

由于您具有dynamic值,因此需要使用IF或CASE来更新列。 它变得有点丑,但它应该工作。

用你的例子,你可以这样做:

更新表SET SET Col1 = CASE ID 
                          当1时1 
                          当2时2 
                          那时4日10 
                           ELSE Col1 
                        结束, 
                  Col2 = CASE ID 
                          那时3日3 
                          当4日12 
                           ELSE Col2 
                        结束
             在哪里(1,2,3,4);

这个问题很老,但我想用另一个答案来扩展这个话题。

我的意思是,实现它的最简单的方法就是用事务包装多个查询。 被接受的答案INSERT ... ON DUPLICATE KEY UPDATE是一个不错的破解,但是应该意识到它的缺点和限制:

  • 如上所述,如果您碰巧使用表中不存在主键的行启动查询,则该查询将插入新的“半成品”logging。 可能这不是你想要的
  • 如果你有一个没有默认值的非空字段的表,并且不希望在查询中触及这个字段,你会得到"Field 'fieldname' doesn't have a default value"即使你"Field 'fieldname' doesn't have a default value" MySQL警告'不要插入一行。 它会让你陷入麻烦,如果你决定是严格的,并将mysql警告转变为你的应用程序的运行时exception。

我对三种build议变体进行了一些性能testing,其中包括INSERT ... ON DUPLICATE KEY UPDATE变体,带有“case / when / then”子句的变体以及带有事务的幼稚方法。 你可能会在这里得到Python代码和结果。 总体的结论是,带有case语句的变体是两个其他变体的两倍,但是为它编写正确和注入安全的代码是相当困难的,所以我个人坚持最简单的方法:使用事务。

编辑: Dakusan的调查结果certificate,我的performance估计不是很有效。 请看这个答案进行另一个更详细的研究。

不知道为什么还没有提到另一个有用的选项:

 UPDATE my_table m JOIN ( SELECT 1 as id, 10 as _col1, 20 as _col2 UNION ALL SELECT 2, 5, 10 UNION ALL SELECT 3, 15, 30 ) vals ON m.id = vals.id SET col1 = _col1, col2 = _col2; 

以下全部适用于InnoDB。

我觉得三种不同方法的速度是重要的。

有三种方法:

  1. INSERT:使用ON DUPLICATE KEY UPDATE进行插入
  2. 交易:您在哪里为交易中的每条logging进行更新
  3. CASE:在哪一种情况下/何时UPDATE中的每个不同的logging

我只是testing了这一点,INSERT方法比TRANSACTION方法快了6.7倍 。 我尝试了一组3000和30000行。

TRANSACTION方法仍然需要运行每个单独的查询,这需要花费时间,虽然它在执行时将结果批量存储在内存中。 在复制和查询日志中,TRANSACTION方法也相当昂贵。

更糟糕的是,CASE方法的速度比INSERT方法的速度慢31,000倍(比TRANSACTION慢6.1倍)。 在MyISAM中速度要慢75倍 。 INSERT和CASE方法在大约1,000条logging中都破解了。 即使在100条logging中,CASE方法也比较快。

所以一般来说,我觉得INSERT方法是最好的,最容易使用。 查询更小,更容易阅读,只需要处理1个查询。 这适用于InnoDB和MyISAM。

奖金的东西:

INSERT非默认字段问题的解决scheme是暂时closures相关的SQL模式: SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TA‌​BLES",""),"STRICT_AL‌​L_TABLES","") 。 如果您计划恢复sql_mode请确保先保存sql_mode

至于其他评论,我已经看到,说auto_increment使用INSERT方法,我也testing了,似乎并非如此。

运行testing的代码如下。 它还输出.SQL文件,以消除PHP解释器开销

 <? //Variables $NumRows=30000; //These 2 functions need to be filled in function InitSQL() { } function RunSQLQuery($Q) { } //Run the 3 tests InitSQL(); for($i=0;$i<3;$i++) RunTest($i, $NumRows); function RunTest($TestNum, $NumRows) { $TheQueries=Array(); $DoQuery=function($Query) use (&$TheQueries) { RunSQLQuery($Query); $TheQueries[]=$Query; }; $TableName='Test'; $DoQuery('DROP TABLE IF EXISTS '.$TableName); $DoQuery('CREATE TABLE '.$TableName.' (i1 int NOT NULL AUTO_INCREMENT, i2 int NOT NULL, primary key (i1)) ENGINE=InnoDB'); $DoQuery('INSERT INTO '.$TableName.' (i2) VALUES ('.implode('), (', range(2, $NumRows+1)).')'); if($TestNum==0) { $TestName='Transaction'; $Start=microtime(true); $DoQuery('START TRANSACTION'); for($i=1;$i<=$NumRows;$i++) $DoQuery('UPDATE '.$TableName.' SET i2='.(($i+5)*1000).' WHERE i1='.$i); $DoQuery('COMMIT'); } if($TestNum==1) { $TestName='Insert'; $Query=Array(); for($i=1;$i<=$NumRows;$i++) $Query[]=sprintf("(%d,%d)", $i, (($i+5)*1000)); $Start=microtime(true); $DoQuery('INSERT INTO '.$TableName.' VALUES '.implode(', ', $Query).' ON DUPLICATE KEY UPDATE i2=VALUES(i2)'); } if($TestNum==2) { $TestName='Case'; $Query=Array(); for($i=1;$i<=$NumRows;$i++) $Query[]=sprintf('WHEN %d THEN %d', $i, (($i+5)*1000)); $Start=microtime(true); $DoQuery("UPDATE $TableName SET i2=CASE i1\n".implode("\n", $Query)."\nEND\nWHERE i1 IN (".implode(',', range(1, $NumRows)).')'); } print "$TestName: ".(microtime(true)-$Start)."<br>\n"; file_put_contents("./$TestName.sql", implode(";\n", $TheQueries).';'); } 
 UPDATE table1, table2 SET table1.col1='value', table2.col1='value' WHERE table1.col3='567' AND table2.col6='567' 

这应该为你工作。

MySQL手册中提供了多个表的参考。

使用临时表

 // Reorder items function update_items_tempdb(&$items) { shuffle($items); $table_name = uniqid('tmp_test_'); $sql = "CREATE TEMPORARY TABLE `$table_name` (" ." `id` int(10) unsigned NOT NULL AUTO_INCREMENT" .", `position` int(10) unsigned NOT NULL" .", PRIMARY KEY (`id`)" .") ENGINE = MEMORY"; query($sql); $i = 0; $sql = ''; foreach ($items as &$item) { $item->position = $i++; $sql .= ($sql ? ', ' : '')."({$item->id}, {$item->position})"; } if ($sql) { query("INSERT INTO `$table_name` (id, position) VALUES $sql"); $sql = "UPDATE `test`, `$table_name` SET `test`.position = `$table_name`.position" ." WHERE `$table_name`.id = `test`.id"; query($sql); } query("DROP TABLE `$table_name`"); } 

有一个你可以修改的设置叫做'多语句',它禁止MySQL的'安全机制'被实现来防止(多于一个)注入命令。 对于MySQL的“精彩”实现来说,它也阻止用户进行有效的查询。

这里( http://dev.mysql.com/doc/refman/5.1/en/mysql-set-server-option.html )是关于设置的C实现的一些信息。

如果你正在使用PHP,你可以使用mysqli做多语句(我认为php已经与mysqli一段时间了)

 $con = new mysqli('localhost','user1','password','my_database'); $query = "Update MyTable SET col1='some value' WHERE id=1 LIMIT 1;"; $query .= "UPDATE MyTable SET col1='other value' WHERE id=2 LIMIT 1;"; //etc $con->multi_query($query); $con->close(); 

希望有所帮助。

你也可能有兴趣在更新上使用连接,这也是可能的。

 Update someTable Set someValue = 4 From someTable s Inner Join anotherTable a on s.id = a.id Where a.id = 4 -- Only updates someValue in someTable who has a foreign key on anotherTable with a value of 4. 

编辑:如果你正在更新的值不是来自数据库的其他地方,你需要发出多个更新查询。

你可以给同一个表添加你想要插入的id(如果你正在逐行更新:

 UPDATE table1 tab1, table1 tab2 -- alias references the same table SET col1 = 1 ,col2 = 2 . . . WHERE tab1.id = tab2.id; 

另外,显然你也可以从其他表更新。 在这种情况下,更新双倍作为一个“SELECT”语句,给你从你指定的表中的数据。 您明确地在查询中声明更新值,所以第二个表不受影响。

为什么没有人在一个查询中提到多个陈述

在PHP中,你使用mysqli实例的multi_query方法。

从PHP手册

MySQL可以允许在一个语句string中有多个语句。 一次发送多个语句减less了客户端 – 服务器往返,但需要特殊的处理。

这里是比较其他3种方法更新30,000原始数据的结果。 代码可以在这里find这是基于@Dakusan的答案

交易:5.5194580554962
插入:0.20669293403625
案例:16.474853992462
多:0.0412278175354

正如你所看到的,多个语句查询比最高的答案更有效率。

如果你得到像这样的错误信息:

 PHP Warning: Error while sending SET_OPTION packet 

您可能需要增加mysqlconfiguration文件中的max_allowed_packet ,在我的机器上是/etc/mysql/my.cnf ,然后重新启动mysqld。

使用

 REPLACE INTO`table` VALUES (`id`,`col1`,`col2`) VALUES (1,6,1),(2,2,3),(3,9,5),(4,16,8); 

请注意:

  • ID必须是主要的唯一键
  • 如果使用外键引用表,则REPLACE将删除然后插入,所以这可能会导致错误

以下将更新一个表中的所有行

 Update Table Set Column1 = 'New Value' 

下一个将更新列2的值大于5的所有行

 Update Table Set Column1 = 'New Value' Where Column2 > 5 

Unkwntech有更新多个表格的例子

 UPDATE table1, table2 SET table1.col1 = 'value', table2.col1 = 'value' WHERE table1.col3 = '567' AND table2.col6='567' 

可以使用INSERT ON DUPLICATE KEY UPDATE sql语句。语法:INSERT INTO table_name(a,b,c)VALUES(1,2,3),(4,5,6)ON DUPLICATE KEY UPDATE a = VALUES(A),b = VALUES(b),C = VALUES(c)中

用PHP我做到了。 使用分号,将其分割成数组,然后通过循环提交。

 $con = new mysqli('localhost','user1','password','my_database'); $batchUpdate = true; /*You can choose between batch and single query */ $queryIn_arr = explode(";", $queryIn); if($batchUpdate) /* My SQL prevents multiple insert*/ { foreach($queryIn_arr as $qr) { if(strlen($qr)>3) { //echo '<br>Sending data to SQL1:<br>'.$qr.'</br>'; $result = $conn->query($qr); } } } else { $result = $conn->query($queryIn); } $con->close(); 
 UPDATE tableName SET col1='000' WHERE id='3' OR id='5' 

这应该达到你想要的。 只需添加更多的ID。 我已经testing过了。

 UPDATE `your_table` SET `something` = IF(`id`="1","new_value1",`something`), `smth2` = IF(`id`="1", "nv1",`smth2`), `something` = IF(`id`="2","new_value2",`something`), `smth2` = IF(`id`="2", "nv2",`smth2`), `something` = IF(`id`="4","new_value3",`something`), `smth2` = IF(`id`="4", "nv3",`smth2`), `something` = IF(`id`="6","new_value4",`something`), `smth2` = IF(`id`="6", "nv4",`smth2`), `something` = IF(`id`="3","new_value5",`something`), `smth2` = IF(`id`="3", "nv5",`smth2`), `something` = IF(`id`="5","new_value6",`something`), `smth2` = IF(`id`="5", "nv6",`smth2`) 

/ /你只是build立在PHP像

 $q = 'UPDATE `your_table` SET '; foreach($data as $dat){ $q .= ' `something` = IF(`id`="'.$dat->id.'","'.$dat->value.'",`something`), `smth2` = IF(`id`="'.$dat->id.'", "'.$dat->value2.'",`smth2`),'; } $q = substr($q,0,-1); 

所以你可以用一个查询来更新孔表