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

时间:2012-07-19 09:18:00

标签: php mysql sql database performance

我试图在我的数据库中更新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 = ""; 
        }


    }

5 个答案:

答案 0 :(得分:4)

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

准备简单语句(如

)的实际CPU成本
  

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

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

您需要在此重新考虑整体策略,尤其是如果您同时使用songs表作为通过Web前端进行交互式请求的资源。更新100K行需要一些时间 - 如果你要在存储顺序中更新100K 100K,那就更少了,因为这将与MYD组织更加一致,写入缓存会更好;更多,如果您以1M行的随机顺序更新100K行,其中PIO的数量将更多。

当你这样做时,你的D / B的整体表现将会严重降低。

  • 您是否希望尽量减少对数据库并行使用的影响,或者您只是尝试将其作为专用批处理操作与其他服务脱机?

  • 您的目标是最大限度地减少总耗用时间或保持合理的时间,但要受到一些整体影响的限制,甚至只是在没有死亡的情况下完成。

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

无论如何,我会放弃批量大小。 multi_query实质上优化了调用进程外mysqld所涉及的RPC over头。一批10人说减少了90%。在此之后你的收益递减 - 特别是说更新将是物理I / O密集型。

答案 1 :(得分:1)

使用预准备语句尝试此代码:

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

答案 2 :(得分:1)

我会做这样的事情:

  $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,但是包含在一个事务中。

答案 3 :(得分:0)

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

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

之前:小时现在:秒!

答案 4 :(得分:0)

我建议在执行多个更新之前锁定表并禁用键。 这样可以避免数据库引擎停止(至少在我的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;