MySQL中的多个更新

Tei*_*ion 363 mysql sql sql-update

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

编辑:例如我有以下内容

Name   id  Col1  Col2
Row1   1    6     1
Row2   2    2     3
Row3   3    9     5
Row4   4    16    8
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

Mic*_*are 616

是的,这是可能的 - 您可以使用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);
Run Code Online (Sandbox Code Playgroud)

  • 注意:此答案还假设ID是主键 (32认同)
  • 如果没有重复,那么我不希望插入该行.应该怎么做?因为我从另一个维护带有id的表的站点获取信息.我正在插入关于该id的值.如果该网站有新记录,那么除了所有其他信息外,我最终只会插入ID和计数.当且仅当有一个id的条目然后它应该更新否则它应该跳过.我该怎么办? (20认同)
  • @HaralanDobrev使用INSERT IGNORE仍会插入非重复记录.Jayapal希望避免的.INSERT IGNORE只是将任何错误变成警告:( http://stackoverflow.com/questions/548541/insert-ignore-vs-insert-on-duplicate-key-update/548570#comment361469_548575 (15认同)
  • @JayapalChandran你应该使用INSERT IGNORE和ON DUPLICATE KEY UPDATE.http://dev.mysql.com/doc/refman/5.5/en/insert.html (10认同)
  • 这个答案假设ID是一个唯一的密钥(可以像其他人所说的那样是主要密钥),但更重要的是它假设没有其他唯一密钥.如果有的话,它可以在工作中抛出扳手. (2认同)
  • 你也应该关心你的增量(如果它存在),如果你插入ODKU它也会增加,即使没有插入新记录!如果您以这种方式进行大量更新,您的自动增量将很快溢出! (2认同)
  • 另一个需要注意的是,任何声明为“NOT NULL”但没有默认值的附加列都必须包含在插入中。不过,它们可以在更新中被忽略,因此您可以使用虚拟值。 (2认同)

小智 117

由于您具有动态值,因此需要使用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);


Rom*_*lov 84

问题是陈旧的,但我想用另一个答案扩展这个话题.

我的观点是,实现它的最简单方法就是用事务包装多个查询.接受的答案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语句的变体的速度是其他两个变体的两倍,但是为它编写正确且注入安全的代码却很难,所以我个人坚持使用最简单的方法:使用事务.

编辑:Dakusan的调查结果证明我的表现估计不是很有效.请参阅此答案,进行另一项更精细的研究.


new*_*ver 65

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

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;
Run Code Online (Sandbox Code Playgroud)

  • 这是最好的.特别是如果你正在从我正在做的另一个SQL查询中提取更新的值. (4认同)

Dak*_*san 33

以下所有内容均适用于InnoDB.

我知道3种不同方法的速度很重要.

有3种方法:

  1. INSERT:INSERT与ON DUPLICATE KEY UPDATE
  2. TRANSACTION:您在事务中为每条记录执行更新的位置
  3. 情况:在UPDATE中为每个不同记录的情况/时间

我只测试了这个,而且INSERT方法比TRANSACTION方法快6.7倍.我尝试了一组3,000和30,000行.

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

更糟的是,CASE方法是41.1x比INSERT方法W/30000条记录(超过TRANSACTION 6.1倍慢)慢.和75X较慢的MyISAM.INSERT和CASE方法在~1,000条记录中均匀收获.即使在100条记录中,CASE方法也更快.

所以一般来说,我觉得INSERT方法最好也最容易使用.查询更小,更易于阅读,只占用1个操作查询.这适用于InnoDB和MyISAM.

奖金:

INSERT非默认字段问题的解决方案是暂时关闭相关的SQL模式:SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TA??BLES",""),"STRICT_AL??L_TABLES","").sql_mode如果您计划还原它,请务必保存第一个.

至于我看过的其他评论说auto_increment使用INSERT方法上升,我也测试了它,但似乎并非如此.

运行测试的代码如下.它还输出.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).';');
}
Run Code Online (Sandbox Code Playgroud)

  • 你在这里做主的工作;) 非常感谢。 (3认同)
  • 我的代码的重点是将计时结果限制为严格的 SQL 语句,而不是语言或库的代码。GoLang 和 PHP 是两种完全独立的语言,用于完全不同的事物。PHP 适用于在单线程上运行的脚本环境,主要是有限的和被动的垃圾收集。GoLang 适用于长时间运行的编译应用程序,将主动垃圾收集和多线程作为主要语言功能之一。它们在语言功能和原因方面几乎没有什么不同。[继续] (2认同)

Unk*_*ech 8

UPDATE table1, table2 SET table1.col1='value', table2.col1='value' WHERE table1.col3='567' AND table2.col6='567'
Run Code Online (Sandbox Code Playgroud)

这应该适用于你.

有在参考MySQL手册为多个表.


Lay*_*ain 8

使用临时表

// 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`");
}
Run Code Online (Sandbox Code Playgroud)


mon*_*oke 5

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

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

来自php 手册

MySQL 允许在一个语句字符串中包含多个语句。一次发送多个语句可以减少客户端-服务器的往返次数,但需要特殊处理。

这是与 update 30,000 raw 中其他 3 种方法相比的结果。可以在此处找到代码该代码基于@Dakusan 的回答

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

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

如果您收到这样的错误消息:

PHP Warning:  Error while sending SET_OPTION packet
Run Code Online (Sandbox Code Playgroud)

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