【问题标题】:optimizing insertion of data into mysql优化将数据插入mysql
【发布时间】:2013-05-22 05:42:12
【问题描述】:
function generateRandomData(){
@ $db = new mysqli('localhost','XXX','XXX','scores');
if(mysqli_connect_errno()) {
    echo 'Failed to connect to database. Please try again later.';
    exit;
}
$query = "insert into scoretable values(?,?,?)";
for($a = 0; $a < 1000000; $a++)
{
$stmt = $db->prepare($query);

$id = rand(1,75000);

$score = rand(1,100000);

$time = rand(1367038800 ,1369630800);
$stmt->bind_param("iii",$id,$score,$time);
$stmt->execute();
}

}

我正在尝试用一百万行数据填充 mysql 中的数据表。然而,这个过程非常缓慢。有什么明显的我做错了我可以修复以使其运行得更快吗?

【问题讨论】:

  • 我宁愿建议完全准备查询然后执行一次。这将有效地减少时间
  • 在没有准备好的语句的情况下不会注意到差异。我对 Web 开发有点陌生,我仍然不完全理解的一件事是索引,我的表中没有任何索引。这会影响速度吗?

标签: php mysql database random


【解决方案1】:

看看我创建的this gist。在我的笔记本电脑上插入一百万行大约需要 5 分钟。

【讨论】:

    【解决方案2】:

    正如 cmets 中所暗示的,您需要通过将尽可能多的插入连接在一起来减少查询的数量。在 PHP 中,很容易实现:

    $query = "insert into scoretable values";
    for($a = 0; $a < 1000000; $a++) {
        $id = rand(1,75000);
        $score = rand(1,100000);
        $time = rand(1367038800 ,1369630800);
        $query .= "($id, $score, $time),";
    }
    $query[strlen($query)-1]= ' ';
    

    您可以执行的查询的最大大小有限制,这与max_allowed_packet 服务器设置直接相关(mysql 文档的This page 描述了如何调整该设置以发挥您的优势)。

    因此,您必须减少上述循环计数以达到适当的查询大小,并重复该过程以达到您要插入的总数,方法是用另一个循环包装该代码。

    另一种做法是禁用对要进行批量插入的表的检查约束:

    ALTER TABLE yourtablename DISABLE KEYS;
    SET FOREIGN_KEY_CHECKS=0;
    -- bulk insert comes here
    SET FOREIGN_KEY_CHECKS=1;
    ALTER TABLE yourtablename ENABLE KEYS;
    

    但是,这种做法必须小心谨慎,尤其是在您的情况下,因为您是随机生成值的。如果您在生成的列中有任何唯一键,则不能按原样将该技术用于您的查询,因为它可能会生成重复的键插入。你可能想给它添加一个IGNORE 子句:

    $query = "insert INGORE into scoretable values";
    

    这将导致服务器静默忽略唯一键上的重复条目。要达到所需插入的总数,只需循环所需的次数以填充剩余的缺失行。

    我想唯一可以有唯一键约束的地方是id 列。在这种情况下,您将永远无法达到您希望拥有的行数,因为它远高于您为该字段生成的随机值范围。考虑提高这个限制,或者更好的是,以不同的方式生成您的 id(也许只需使用一个计数器,这将确保每条记录使用不同的键)。

    【讨论】:

      【解决方案3】:

      数据库查询涉及许多相互关联的任务。因此,这是一个“昂贵”的过程。在插入/更新方面,它甚至更加“昂贵”。

      运行一次查询是提高性能的最佳方式。

      您可以准备循环中的语句并运行一次。

      例如。

      $query = "insert into scoretable values ";
      for($a = 0; $a < 1000000; $a++)
      {
      $values = " ('".$?."','".$?."','".$?."'), ";
      $query.=$values;
      ...
      
      
      }
      ...
      //remove the last comma
      ...
      $stmt = $db->prepare($query);
      ...
      $stmt->execute();
      

      【讨论】:

        【解决方案4】:

        你做错了几件事。您必须考虑的第一件事是您使用的是什么 MySQL 引擎。

        默认引擎是 InnoDB,之前默认引擎是 MyISAM。

        我将在假设您使用 InnoDB 的情况下编写此答案,出于多种原因您应该使用它。 InnoDB 以一种称为自动提交模式的方式运行。这意味着您所做的每个查询都包含在事务中。 要将其翻译成我们普通人可以理解的语言 - 您执行的每个查询没有指定 BEGIN WORK; 块是一个事务 - 因此,MySQL 将等到硬盘驱动器确认数据已写入。 p>

        知道硬盘驱动器很慢(机械驱动器仍然是最广泛使用的驱动器),这意味着您的插入速度将与硬盘驱动器一样快。通常,机械硬盘每秒可以执行大约 300 次输入输出操作,因此假设您每秒可以执行 300 次插入 - 是的,您将等待相当长的时间才能插入 100 万条记录。

        因此,了解事物的运作方式 - 您可以利用它们来发挥自己的优势。

        HDD 每次事务写入的数据量通常会非常小(4KB 甚至更少),并且知道今天的 HDD 可以写入超过 100MB/秒 - 这表明我们应该将多个查询包装到单个事务中。

        这样,MySQL 将发送相当多的数据并等待 HDD 确认它已写入所有内容,并且整个世界都很好。

        因此,假设您要填充 1M 行 - 您将执行 1M 查询。如果您的事务一次提交 1000 个查询,您应该只执行大约 1000 个写入操作。

        这样,你的代码就变成了这样的东西

        (我不熟悉 mysqli 接口,所以函数名称可能是错误的,并且看到我在没有实际运行代码的情况下输入 - 该示例可能无法正常工作,因此使用它需要您自担风险)

        function generateRandomData()
        {
            $db = new mysqli('localhost','XXX','XXX','scores');
        
            if(mysqli_connect_errno()) {
                echo 'Failed to connect to database. Please try again later.';
                exit;
            }
        
            $query = "insert into scoretable values(?,?,?)";
        
            // We prepare ONCE, that's the point of prepared statements
            $stmt = $db->prepare($query);
        
            $start = 0;
            $top = 1000000;
        
            for($a = $start; $a < $top; $a++)
            {
                // If this is the very first iteration, start the transaction
                if($a == 0)
                {
                    $db->begin_transaction();
                }
                $id = rand(1,75000);
        
                $score = rand(1,100000);
        
                $time = rand(1367038800 ,1369630800);
        
                $stmt->bind_param("iii",$id,$score,$time);
                $stmt->execute();
        
        
                // Commit on every thousandth query
                if( ($a % 1000) == 0 && $a != ($top - 1) )
                {
                    $db->commit();
        
                    $db->begin_transaction();
                }
        
                // If this is the very last query, then we just need to commit and end
                if($a == ($top - 1) )
                {
                    $db->commit();
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2010-12-18
          • 2018-04-08
          • 1970-01-01
          • 2023-04-10
          • 1970-01-01
          • 2011-06-14
          • 2015-08-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多