0
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

4 回答 4

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();
于 2013-05-27T11:59:11.503 回答
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 文档的此页面描述了如何调整该设置以发挥您的优势)。

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

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

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(也许只需使用计数器,这将确保每条记录使用不同的键)。

于 2013-05-27T12:17:41.393 回答
1

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

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

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

知道硬盘驱动器很慢(机械驱动器仍然是最广泛使用的驱动器),这意味着您的插入将与硬盘驱动器一样快。通常,机械硬盘每秒可以执行大约 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();
        }
    }
}
于 2013-05-27T11:52:47.247 回答
0

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

于 2013-06-20T10:17:27.587 回答