优化将数据插入到mysql中

时间:2013-05-27 11:15:18

标签: php mysql database random

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中的数据表。但是,这个过程非常缓慢。是否有任何明显的错误我可以修复以使其运行得更快?

4 个答案:

答案 0 :(得分:1)

你做错了几件事。首先要考虑的是你正在使用的MySQL引擎。

默认的是InnoDB,以前的默认引擎是MyISAM。

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

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

因此,了解事情的运作方式 - 您可以利用它们。

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

这样MySQL就会发送相当多的数据并等待硬盘确认它写了所有内容,并且整个世界都很好用。

因此,假设您要填充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();
        }
    }
}

答案 1 :(得分:1)

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

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

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

例如。

$query = "insert into scoretable values ";
for($a = 0; $a < 1000000; $a++)
{
$values = " ('".$?."','".$?."','".$?."'), ";
$query.=$values;
...


}
...
//remove the last comma
...
$stmt = $db->prepare($query);
...
$stmt->execute();

答案 2 :(得分:1)

正如评论中暗示的那样,您需要通过连接尽可能多的插入来减少查询数量。在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 :(得分:0)

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