431

我知道您可以一次插入多行,有没有办法在 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;
4

18 回答 18

703

是的,这是可能的 - 您可以使用 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);
于 2008-08-06T14:33:41.560 回答
139

由于您有动态值,因此您需要使用 IF 或 CASE 来更新要更新的列。它有点难看,但它应该工作。

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

更新表 SET Col1 = CASE id
                          当 1 时 1
                          当 2 然后 2
                          当 4 然后 10
                          其他 Col1
                        结尾,
                 Col2 = 案例编号
                          当 3 那么 3
                          当 4 然后 12
                          ELSE Col2
                        结尾
             WHERE id IN (1, 2, 3, 4);
于 2008-09-17T14:55:02.717 回答
95

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

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

  • 如前所述,如果您碰巧使用表中不存在主键的行启动查询,则查询会插入新的“半生不熟”记录。可能不是你想要的
  • 如果你有一个非空字段且没有默认值的表,并且不想在查询中触及该字段,那么"Field 'fieldname' doesn't have a default value"即使你根本不插入一行,你也会收到 MySQL 警告。如果您决定严格并在您的应用程序中将 mysql 警告转换为运行时异常,它将给您带来麻烦。

我对三个建议的变体进行了一些性能测试,包括INSERT ... ON DUPLICATE KEY UPDATE变体、带有“case / when / then”子句的变体和带有事务的幼稚方法。您可以在此处获得 python 代码和结果。总体结论是,带有 case 语句的变体的速度是其他两个变体的两倍,但是很难为其编写正确且注入安全的代码,因此我个人坚持使用最简单的方法:使用事务。

编辑:Dakusan的调查结果证明我的性能估计不太有效。请参阅此答案以进行另一项更详细的研究。

于 2013-06-24T20:14:12.363 回答
82

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

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;
于 2013-09-26T16:01:40.237 回答
50

以下所有内容都适用于 InnoDB。

我觉得了解三种不同方法的速度很重要。

有3种方法:

  1. 插入:插入重复键更新
  2. TRANSACTION:您在其中对事务中的每条记录进行更新
  3. 案例:对于 UPDATE 中的每个不同记录,您的案例/时间

我刚刚对此进行了测试,对我来说,INSERT 方法比 TRANSACTION 方法快6.7 倍。我尝试了一组 3,000 和 30,000 行。

TRANSACTION 方法仍然必须运行每个单独的查询,这需要时间,尽管它在执行时将结果批处理到内存或其他东西中。TRANSACTION 方法在复制和查询日志中也非常昂贵。

更糟糕的是,CASE 方法比具有 30,000 条记录的 INSERT 方法慢 41.1 倍(比TRANSACTION慢 6.1 倍)。在MyISAM中慢了 75 倍。INSERT 和 CASE 方法在大约 1,000 条记录时实现了收支平衡。即使有 100 条记录,CASE 方法也几乎没有更快的速度。

所以总的来说,我觉得 INSERT 方法是最好的,也是最容易使用的。查询更小更易于阅读,并且只占用 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 解释器开销

<?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).';');
}
于 2016-10-03T11:58:05.807 回答
10
UPDATE table1, table2 SET table1.col1='value', table2.col1='value' WHERE table1.col3='567' AND table2.col6='567'

这应该对你有用。

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

于 2008-08-06T14:14:33.737 回答
9

使用临时表

// 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`");
}
于 2011-04-07T07:34:08.543 回答
6

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

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

来自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。

于 2017-07-05T16:23:20.460 回答
3

您可以更改一个名为“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();

希望有帮助。

于 2011-03-06T21:32:06.220 回答
3

您可以为同一个表设置别名,以便为您提供要插入的 id(如果您正在进行逐行更新:

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

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

于 2013-01-02T19:48:31.147 回答
2

您可能还对在更新上使用连接感兴趣,这也是可能的。

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.

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

于 2008-08-06T14:20:23.257 回答
0

是的..可以使用 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=值(a),b=值(b),c=值(c)

于 2014-08-09T10:16:59.350 回答
0

采用

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

请注意:

  • id 必须是主唯一键
  • 如果您使用外键来引用表,REPLACE 删除然后插入,所以这可能会导致错误
于 2016-03-15T16:59:23.193 回答
0

现在最简单的方法

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
于 2020-05-06T19:43:08.670 回答
0

我从@newtover 那里得到答案,并使用 MySql 8 中的新 json_table 函数对其进行了扩展。这允许您创建一个存储过程来处理工作负载,而不是在代码中构建自己的 SQL 文本:

drop table if exists `test`;
create table `test` (
  `Id` int,
  `Number` int,
  PRIMARY KEY (`Id`)
);
insert into test (Id, Number) values (1, 1), (2, 2);

DROP procedure IF EXISTS `Test`;
DELIMITER $$
CREATE PROCEDURE `Test`(
    p_json json
)
BEGIN
    update test s
        join json_table(p_json, '$[*]' columns(`id` int path '$.id', `number` int path '$.number')) v 
        on s.Id=v.id set s.Number=v.number;
END$$
DELIMITER ;

call `Test`('[{"id": 1, "number": 10}, {"id": 2, "number": 20}]');
select * from test;

drop table if exists `test`;

它比纯 SQL 慢了几毫秒,但我很乐意接受这一点,而不是在代码中生成 sql 文本。不确定它在处理大型记录集时的性能如何(JSON 对象的最大大小为 1Gb),但我在一次更新 10k 行时一直使用它。

于 2021-01-29T08:02:38.870 回答
-2

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

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'
于 2008-08-06T14:18:21.217 回答
-4
UPDATE tableName SET col1='000' WHERE id='3' OR id='5'

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

于 2008-08-06T14:22:37.837 回答
-6
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);

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

于 2013-08-28T15:40:27.173 回答