如何更新mysql数据库中的数千行

Posted

技术标签:

【中文标题】如何更新mysql数据库中的数千行【英文标题】:How to update thousands of rows in mysql database 【发布时间】:2012-07-19 09:17:59 【问题描述】:

我试图更新我的数据库中的 100.000 行,下面的代码应该这样做,但我总是得到一个错误:

错误:命令不同步;你现在不能运行这个命令

因为它是一个更新,所以我不需要结果,只想摆脱它们。使用 $count 变量是为了让我的数据库获得大量更新而不是一次大更新。 (由于数据库的某些限制,一个大更新无法正常工作)。

我尝试了很多不同的东西,比如 mysqli_free_result 等等......没有任何效果。

    global $mysqliObject;

    $count = 0;    
    $statement = "";

    foreach ($songsArray as $song) 

        $id = $song->getId();
        $treepath = $song->getTreepath();

        $statement = $statement."UPDATE songs SET treepath='".$treepath."' WHERE id=".$id."; ";
        $count++;

        if ($count > 10000)

            $result = mysqli_multi_query($mysqliObject, $statement);

            if(!$result) 
                 die('<br/><br/>Error1: ' . mysqli_error($mysqliObject));    
            



            $count = 0;

            $statement = ""; 
        


    

【问题讨论】:

dev.mysql.com/doc/refman/5.0/en/commands-out-of-sync.html 这是准备好的语句的绝对理想人选。它们将更易于使用且效率更高。我个人从不使用_multi_query(),麻烦大于价值。 我从来没有用过准备好的陈述,你能用准备好的陈述代替我的回答吗 @SebastianOberste-Vorth $treepath 是什么类型的数据?是字符串吗? 【参考方案1】:

按照 DaveRandom 和 StevenVI 的建议,使用准备好的查询减少 mysqld 进程中的 CPU 负载。但是在这种情况下,我怀疑使用准备好的查询会对您的运行时产生重大影响。您面临的挑战是您尝试更新 songs 表中的 100K 行,这将涉及物理磁盘子系统上的大量物理 I/O。正是这些物理延迟(比如每个 PIO 约 10 毫秒)将主导运行时。诸如每行中包含的内容、您在表上使用多少索引(尤其是那些涉及树路径的索引)等因素都将融入这种组合。

准备一个简单语句的实际 CPU 成本

UPDATE songs SET treepath="some treepath" WHERE id=12345;

将在整个物理 I/O 延迟中丢失,其相对大小在很大程度上取决于您存储数据的物理子系统的性质:单个 SATA 磁盘;固态硬盘;一些具有大缓存和 SSD 支持的 NAS ...

您需要在这里重新考虑您的整体策略,特别是如果您同时使用songs 表作为通过网络前端进行交互式请求的资源。更新 100K 行将需要一些时间——如果您按存储顺序更新 100K 中的 100K,则时间会更少,因为这将更符合 MYD 组织,并且写入缓存会更好;如果您在 1M 行中以随机顺序更新 100K 行,则更多,其中 PIO 的数量会更多。

当你这样做时,你的 D/B 的整体性能会严重下降。

您是想尽量减少对并行使用数据库的影响,还是只是尝试将其作为专用批处理操作与其他服务离线?

您的目标是最小化总运行时间,还是在某些整体影响约束下保持合理的短,或者甚至只是在不死的情况下完成。

我建议您有两种明智的方法:(i) 将此作为适当的批处理活动,将 D/B 离线到其他服务。在这种情况下,您可能希望对表进行锁定,并使用 ALTER TABLE ... DISABLE/ENABLE KEYS 将更新括起来。 (ii) 以小得多的更新集和每组之间的延迟以允许 D/B 刷新到磁盘的方式进行涓流更新。

无论如何,我会降低批量大小。 multi_query 从本质上优化了调用进程外 mysqld 所涉及的 RPC 开销。一批 10 人说减少了 90%。在此之后您的收益会递减——尤其是说更新将是物理 I/O 密集型的。

【讨论】:

+1 现在这是一个半答案。底层存储引擎对此有何不同? MEMORY 完全消除了磁盘 I/O 问题,因此准备好的语句将显着提高性能(?),但以现实世界中常见的问题为例——MyISAM 与 InnoDB——这会对磁盘 I/O 产生什么样的影响活动?我认为 MyISAM 在并行使用场景中的性能会因为更新期间的表级锁定(?)而变得更糟,但是在专用的批处理作业中,这会更快吗? 是的,MyISAM 的另一个杀手锏是它不做数据缓存,所以任何更新都会立即写回 MYD 文件——尽管是通过 *nix 上的 VFAT 直写机制.是的,批处理作业将花费更少的时间,但是如果 OP 不想在长时间更新期间取消他的在线服务怎么办?一般来说,建议将 InnoDB 用于更新密集型表。 感谢您的帮助。我会为大家+1,我学到了很多...最后我找到了另一种简单的方法,因为我可以“离线”(DROP + INSERT) @SebastianOberste-Vorth,如果您打算通过完全重新创建离线操作,然后 TRUNCATE 表,禁用键,执行批量插入,重新启用键和优化表格:-)【参考方案2】:

使用准备好的语句试试这段代码:

// Create a prepared statement
$query = "
  UPDATE `songs`
  SET `treepath` = ?
  WHERE `id` = ?
";
$stmt = $GLOBALS['mysqliObject']->prepare($query); // Global variables = bad

// Loop over the array
foreach ($songsArray as $key => $song) 

  // Get data about this song
  $id = $song->getId();
  $treepath = $song->getTreepath();

  // Bind data to the statement
  $stmt->bind_param('si', $treepath, $id);

  // Execute the statement
  $stmt->execute();

  // Check for errors
  if ($stmt->errno) 
    echo '<br/><br/>Error: Key ' . $key . ': ' . $stmt->error;
    break;
   else if ($stmt->affected_rows < 1) 
    echo '<br/><br/>Warning: No rows affected by object at key ' . $key;
  

  // Reset the statment
  $stmt->reset();



// We're done, close the statement
$stmt->close();

【讨论】:

但是这段代码不是更新了数据库 100.000 次吗?我在没有准备好的语句的情况下做了类似的事情,这花了很长时间,这就是我尝试 multi_query 的原因,不幸的是这给了我错误 @SebastianOberste-Vorth 由于准备好的语句的工作方式,实际上这里的幕后工作要做的更少。因为正在准备语句,所以每次执行只将 data 发送到服务器,而不是整个 SQL 语句。该语句只解析一次(调用prepare() 时),从长远来看,这将更快更高效, 阅读 php 页面关于准备好的语句。逻辑很简洁(为了更好的解释)。 我刚刚根据我之前的代码测试了您的代码(每首歌曲的一个更新,没有准备好的语句)。他们在同一时间运行! :( @SebastianOberste-Vorth $key 的唯一一点是您可以看到输入数组中发生错误的位置。如果你不想要这些信息,你可以去掉它。【参考方案3】:

我会这样做:

  $link = mysqli_connect('host');
  if ( $stmt = mysqli_prepare($link, "UPDATE songs SET treepath=? WHERE id=?") ) 

    foreach ($songsArray as $song) 

        $id = $song->getId();
        $treepath = $song->getTreepath();

        mysqli_stmt_bind_param($stmt, 's', $treepath); // Assuming it's a string...
        mysqli_stmt_bind_param($stmt, 'i', $id);
        mysqli_stmt_execute($stmt);
    
    mysqli_stmt_close($stmt);
  
  mysqli_close($link);

或者当然你是普通的mysql_query,但包含在事务中。

【讨论】:

就像上面的评论 - 这段代码是否更新了数据库 100.000 次?我的代码必须快速执行... 如果歌曲数组有 100.000 项大,是的。但我强烈建议不要在 PHP 中使用如此庞大的数组。您的初始代码也将数据库更新 10 万次。但速度较慢,因为 SQL 服务器必须一遍又一遍地解析每个查询。在这里你解析一次,然后发送新的值来处理。 为了计算新的树路径,我需要数据库的所有条目。我将它们存储在脚本的开头并一直使用它们 - 最后也用于更新。什么是不存储所有条目并且仍然能够计算数据库中所有条目的平均值等更好的解决方案?谢谢!【参考方案4】:

我找到了另一种方法...

由于这不是生产服务器 - 更新 100k 行的最快方法是删除所有行并使用新的计算值从头开始插入 100k。删除所有内容并插入所有内容而不是更新似乎有点奇怪,但它更快。

之前:小时 现在:秒!

【讨论】:

如果需要几个小时之前,我建议您的问题实际上是索引。 100K 行在总体方案中并不是很多,使用正确设置的主键(并使用主键来定位需要更新的记录)这应该不会超过几秒钟,即使在繁忙的服务器。 这是有效的,因为您正在删除 所有 行并有效地重新创建表,因此优化了物理 I/O - 特别是如果您在批量过程中禁用了索引插入。正如我上面所说,如果表包含 100 万行,而你删除然后重新插入 10%,你会有不同的故事。【参考方案5】:

我建议在执行多次更新之前锁定表并禁用键。 这将避免数据库引擎停止(至少在我的 300,000 行更新的情况下)。

LOCK TABLES `TBL_RAW_DATA` WRITE;
/*!40000 ALTER TABLE `TBL_RAW_DATA` DISABLE KEYS */;

UPDATE TBL_RAW_DATA SET CREATION_DATE = ADDTIME(CREATION_DATE,'01:00:00') WHERE ID_DATA >= 1359711;

/*!40000 ALTER TABLE `TBL_RAW_DATA` ENABLE KEYS */;
UNLOCK TABLES;

【讨论】:

以上是关于如何更新mysql数据库中的数千行的主要内容,如果未能解决你的问题,请参考以下文章

将数千条记录插入表中的最有效方法是啥(MySQL,Python,Django)

更新 MySQL 表中的大量行

如何使用 MySQL 中的每一行动态更新所有行? [复制]

如何合并 MySQL 表中的重复行

在 MySQL 中,如何从长文本字符串中提取 URL?

如何使用php更新mysql表中的特定表行[重复]