我知道您可以一次插入多行,是否可以在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;


#1 楼

是的,那是可能的-您可以在重复的密钥更新中使用INSERT...。

使用您的示例:

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);


评论


如果没有重复,那么我不希望插入该行。 id应该怎么做?因为我要从另一个维护带有ID的表的站点获取信息。我正在针对该ID插入值。如果该站点有新记录,那么我最终只会插入ids并计数除所有其他信息之外的所有信息。当且仅当id有一个条目时,它才应更新,否则应跳过。我该怎么办?

–贾巴尔(Jayapal Chandran)
2010年8月30日13:30



注意:此答案还假设ID是主键

– JM4
2012年12月12日18:01

@JayapalChandran,您应该将INSERT IGNORE与ON DUPLICATE KEY UPDATE一起使用。 dev.mysql.com/doc/refman/5.5/en/insert.html

– Haralan Dobrev
2013年9月16日14:15



@HaralanDobrev使用INSERT IGNORE仍会插入非重复的记录。 Jayapal想要避免的。 INSERT IGNORE只是将任何错误变成警告:(stackoverflow.com/questions/548541/…

–足立武宏
13-10-24在17:32

该答案假定ID是唯一键(可以像其他人所说的那样是主键),但更重要的是,它假定没有其他唯一键。如果有的话,它可能会在工作中投入扳手。

–史蒂夫·霍瓦斯(Steve Horvath)
2014年12月3日下午5:43

#2 楼

由于您具有动态值,因此需要对要更新的列使用IF或CASE。它有点难看,但是应该可以。

使用您的示例,您可以这样做:

UPDATE table SET Col1 = CASE id 
                          WHEN 1 THEN 1 
                          WHEN 2 THEN 2 
                          WHEN 4 THEN 10 
                          ELSE Col1 
                        END, 
                 Col2 = CASE id 
                          WHEN 3 THEN 3 
                          WHEN 4 THEN 12 
                          ELSE Col2 
                        END
             WHERE id IN (1, 2, 3, 4);


评论


编写动态更新可能不太好看,但有趣的是关注框的功能...

–me_
17年2月7日在21:55

@ user2536953,它也可以用于动态更新。例如,我在php循环中使用了该解决方案:$ commandTxt ='更新操作SET chunk_finished = CASE id'; foreach($ blockOperationChecked as $ operationID => $ operationChecked)$ commandTxt。=“”当$ operationID THEN $ operationChecked“时; $ commandTxt。='其他ID的ID在哪里ID('.implode(',',array_keys(blockOperationChecked))。');';

– Boolean_Type
17年2月15日在8:43

#3 楼

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

我的意思是,最简单的方法是用事务包装多个查询。公认的答案INSERT ... ON DUPLICATE KEY UPDATE是一个不错的技巧,但您应该意识到它的缺点和局限性:


可以这么说,如果您碰巧使用主键没有的行启动查询,如果表中不存在,查询将插入新的“半熟”记录。可能不是您想要的
如果您的表具有不为空的字段且没有默认值,并且不想在查询中触摸此字段,即使没有插入,也会收到"Field 'fieldname' doesn't have a default value" MySQL警告单行。如果您决定严格要求并将mysql警告转换为应用程序中的运行时异常,它将惹上麻烦。

我对三个建议的变体(包括INSERT ... ON DUPLICATE KEY UPDATE变体)进行了性能测试。使用“ case / when / then”子句和带有事务处理的幼稚方法。您可以在此处获取python代码和结果。总的结论是,带有case语句的变体的速度是其他两个变体的两倍,但是很难为其编写正确且注入安全的代码,因此我个人坚持最简单的方法:使用事务。 br />
编辑:Dakusan的发现证明了我的性能估算不是十分有效。请参阅此答案以进行其他更详尽的研究。

评论


使用交易,非常好(且简单)的提示!

– mTorres
2014年7月30日在17:24

如果我的表不是InnoDB类型怎么办?

– TomeeNS
2014-09-22 18:26

有人可以提供指向执行此操作的交易的链接吗?和/或带有case语句的变体的注入安全代码的代码?

–FrançoisM.
15年7月21日在16:44

我发现这篇文章中有关速度的信息是错误的。我在下面的文章中写到了它。 stackoverflow.com/questions/3432/multiple-updates-in-mysql / ...

– Dakusan
16-10-3在12:07

@Dakusan,好答案。非常感谢您扩展,评论和修正我的结果。

–罗马伊曼库洛夫
16-10-4在7:04

#4 楼

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

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;


评论


这是最好的。特别是如果您像我一样正在从另一个SQL查询中提取更新值。

– v010dya
2014年12月30日上午8:55

这对于更新具有大量列的表非常有用。将来我可能会经常使用此查询。谢谢!

–卡斯珀·威尔克斯
2015年1月9日在20:18

我已经尝试过这种类型的查询。但是当记录达到30k时,边界服务器停止。还有其他解决方案吗?

– Bhavin Chauhan
16-9-27在6:27

这看起来很棒。我将尝试将其与WHERE子句结合使用,在该子句中,主键未更新,但用于标识要更改的列。

–nl-x
3月2日13:30



@BhavinChauhan您是否尝试过使用临时表而不是join-select来解决此问题?

–nl-x
3月2日,13:32



#5 楼

以下所有内容都适用于InnoDB。
我知道知道3种不同方法的速度很重要。
有3种方法:

INSERT:使用ON DUPLICATE KEY UPDATE进行插入
事务:在事务中对每个记录进行更新的地方

我刚刚对此进行了测试,对我而言,INSERT方法比TRANSACTION方法快6.7倍。我尝试了一组3,000行和30,000行。
TRANSACTION方法仍然必须运行每个单独的查询,这会花费一些时间,尽管它会在执行时将结果批处理到内存中。在复制和查询日志中,TRANSACTION方法也非常昂贵。
更糟糕的是,CASE方法比INSERT方法慢31.1倍,具有30,000条记录(比TRANSACTION慢6.1倍)。而MyISAM的速度要慢75倍。 INSERT和CASE方法收支平衡,达到约1,000条记录。即使是100条记录,CASE方法也快得多。这些查询较小,更易于阅读,仅占用1个操作查询。这适用于InnoDB和MyISAM。
奖励内容:
INSERT非默认字段问题的解决方案是暂时关闭相关的SQL模式:SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TABLES",""),"STRICT_ALL_TABLES","")。如果打算恢复它,请确保先保存sql_mode
对于其他注释,我已经看到说auto_increment使用INSERT方法上升了,在InnoDB中似乎确实如此,但在MyISAM中却没有。
运行测试的代码如下。它还输出.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).';');
}
 


评论


您正在这里做耶和华的工作;)非常感谢。

–辣椒
18年1月29日在12:14

测试了GoLang和PHP之间的性能,在MariaDB上使用了40k行,我在PHP上花了2秒,而在golang上花了6秒……。好吧,我总是被告知GoLang的运行速度要比PHP快!所以,我开始想知道如何提高性能...使用INSERT ...在重复键更新上...我在Golang上获得了0.74秒,在PHP上获得了0.86秒!

–迭戈·法韦罗(Diego Favero)
19年4月2日在18:13

我的代码的重点是将计时结果严格限制为SQL语句,而不是语言或库的代码。 GoLang和PHP是2种完全独立的语言,用于完全不同的事物。 PHP适用于在单个线程上的单次运行脚本环境,并且具有大部分受限和被动垃圾回收。 GoLang适用于长期运行的已编译应用程序,其中积极的垃圾回收和多线程是主要语言功能之一。就语言功能和原因而言,它们几乎没有什么不同。 [继续]

– Dakusan
19年4月3日,下午3:35

因此,在运行测试时,请确保将速度测量严格限制为SQL语句的“查询”函数调用。比较和优化源代码中并非严格要求查询的其他部分,就像比较苹果和桔子。如果将结果限制在此范围内(已预编译了字符串并且可以使用),那么结果应该非常相似。此时的任何差异都是该语言的SQL库的错误,而不一定是该语言本身。我认为,INSERT ON DUPLICATE解决方案曾经是并且将永远是最佳选择。[续]

– Dakusan
19年4月3日,下午3:41

至于您对GoLang更快的评论,这是一个令人难以置信的广泛陈述,没有考虑到这些语言及其设计的许多警告或细微差别。 Java是一种解释型语言,但是我发现15年前,它在某些情况下的速度实际上几乎可以与C匹配(甚至有时可以击败C)。 C是一种汇编语言,是除汇编器之外最常见的最低级系统语言。我真的很喜欢GoLang所做的事情,它无疑具有成为最常见的系统之一并经过优化的力量和灵活性[续]

– Dakusan
19年4月3日,下午3:46

#6 楼

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


这适用于ya。

MySQL手册中有多个表的参考。

#7 楼

使用临时表

// 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`");
}


#8 楼

为什么没有人在一个查询中提到多个语句?

在php中,您使用mysqli实例的multi_query方法。

来自php手册


MySQL可以选择在一个语句字符串中包含多个语句。一次发送多个语句可以减少客户端与服务器之间的往返,但是需要特殊的处理。这是在更新30,000 raw中与其他3种方法相比的结果。可在此处找到基于@Dakusan的答案的代码

事务:5.5194580554962
插入:0.20669293403625
案例:16.474853992462
多:0.0412278175354

如您所见,多语句查询比最高答案更有效。

如果出现以下错误消息:

PHP Warning:  Error while sending SET_OPTION packet


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

评论


以下所有比较都是针对INSERT测试进行的。我只是在相同的条件下进行了测试,没有事务,它在300行中慢了145倍,在3000行中慢了753倍。我本来是从30,000行开始的,但是我去做午饭,然后回来,而且还在继续。这是有道理的,因为运行单个查询并将每个查询分别刷新到数据库将非常昂贵。特别是复制。尽管打开事务有很大的不同。在3,000行时,它花了1.5倍,在30,000行时是2.34倍。 [继续]

– Dakusan
17年11月26日在22:04

但是您认为它是快速的(带有事务处理)是正确的。在3,000和30,000行中,它比INSERT方法要快得多。绝对无法通过运行1个查询获得比30,000个查询更好的结果,即使它们是在特殊的MySQL API调用中批量处理的也是如此。只运行300行,它比所有其他方法快得多(令我惊讶),它遵循与CASE方法大致相同的图形曲线。这可以用两种方法解释。第一个是由于“ ON DUPLICATE KEY [cont]”,INSERT方法实际上总是插入两行。

– Dakusan
17年11月26日在22:18



UPDATE”会同时导致“ INSERT”和“ UPDATE”。另一个原因是由于使用索引查找,SQL处理器中一次只能编辑1行的工作量较少。我不确定您如何获得与我不同的结果,但是您的附加测试看起来很可靠,实际上我什至不知道复制将如何处理此调用,这也仅适用于UPDATE调用。使用单个INSERT查询,插入调用总是最快的。

– Dakusan
17年11月26日在22:21

我一次在一个表上执行300次UPDATE,以修改一个for循环内的错误,这花费了41秒。将相同的UPDATE查询放入一个$ mysqli-> multi_query($ sql)花费了“ 0”秒。但是,后续查询失败,导致我将其设为单独的“程序”。

–克里斯K
18年1月11日在18:30



谢谢。使用多个查询可以在一分钟内更新约5k行(没有测试更多)。如果有人正在寻找PDO解决方案:stackoverflow.com/questions/6346674/…

–斯科菲尔德
19年2月20日在19:20

#9 楼

您可以更改一个名为“ multi statement”的设置,该设置将禁用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仍将作为单独的查询处理。更好的方法是将它们包装在一个事务中,然后将更改立即提交到表中。

– Marki555
2014年4月12日15:01

如何将它们包装在一笔交易中?请给我们看。

– TomeeNS
2014-09-22 18:20

@TomeeNS在发送查询之前使用mysqli :: begin_transaction(..),在发送查询之后使用mysql :: commit(..)。或在查询本身中将START TRANSACTION用作第一个语句,将COMMIT用作最后一个语句。

– JuhaPalomäki
16年11月15日在23:55

#10 楼

您可以为同一张表加上别名,以提供您要插入的ID(如果您要逐行更新:

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


此外,它应该很明显,您也可以从其他表中进行更新。在这种情况下,更新将作为“ SELECT”语句加倍,为您提供指定表中的数据。您在查询中明确说明了更新值,第二张表不受影响。

#11 楼

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

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.


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

#12 楼

现在,简单的方法
update my_table m, -- let create a temp table with populated values
    (select 1 as id, 20 as value union -- this part will be generated
     select 2 as id, 30 as value union -- using a backend code
     -- for loop 
     select N as id, X as value
        ) t
set m.value = t.value where t.id=m.id -- now update by join - quick


#13 楼

请使用

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


请注意:如果您使用外键,则


id必须是主唯一键

引用表,REPLACE删除然后插入,因此可能
导致错误


#14 楼

是..可以使用INSERT ON DUPLICATE KEY UPDATE sql语句。.
INSERT INTO table_name(a,b,c)值(1,2,3),(4,5, 6)
重复键更新a = VALUES(a),b = VALUES(b),c = VALUES(c)

#15 楼

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

Update Table Set
Column1 = 'New Value'


下一个将更新Column2的值大于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'


#16 楼

UPDATE tableName SET col1='000' WHERE id='3' OR id='5'


这应该可以实现您想要的。只需添加更多ID。我已经测试过了。

#17 楼

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`) 


//就像在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);


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

评论


我没有投票,但我认为反对是在不需要时进行设置(并且您仍在进行设置,当您将某物设置为某物时)

– v010dya
2014年12月30日在9:04