4

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


    }
4

5 回答 5

4

正如 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表格作为通过 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 开销。一批 10 人说减少了 90%。在此之后您的收益会递减——尤其是说更新将是物理 I/O 密集型的。

于 2012-07-19T12:20:13.890 回答
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();
于 2012-07-19T09:41:06.487 回答
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,但包含在事务中。

于 2012-07-19T09:41:16.110 回答
1

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

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

之前:小时 现在:秒!

于 2012-07-19T14:48:08.087 回答
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;
于 2019-04-10T16:14:07.930 回答