有没有更好/更快的方法来使用PHP和MariaDB执行这些多个插入?

frebpwbc  于 2023-01-16  发布在  PHP
关注(0)|答案(2)|浏览(156)

因此,我尝试建立一个小型数据库来跟踪我们联盟中弹球机上的高分。我有一个用户表,其中只有一个AI ID列和一个电子邮件地址列。然后我有一个游戏表,其中有一个AI ID列和弹球机的名称。由于这是一个多对多的关系,我创建了第三个表scores,其中有user_id、game_id、和得分作为列。
编辑:包括读取代码:

$file = fopen('scores.txt', 'r') or die("Unable to open file.");

// Loop through the file line by line
$line_number = 1;
while (($line = fgets($file)) !== false) {

    // Reset flags
    $email_exists = 0;
    $game_exists = 0;
    if (isset($email_id)) unset($email_id);
    if (isset($game_id)) unset($game_id);
    echo ($line_number . " ");

    // Split the line into components
    $line = rtrim($line);
    $array = explode(",", $line, 3);
    $email = strtolower($array[0]);
    $game = $array[1];
    $score = $array[2];
    $stmt = $db->prepare ("SELECT email_id FROM users WHERE email_address = ?");
    $stmt->execute(array($email))
        if ($stmt->rowCount() < 1) {
        $stmt = $db->prepare("INSERT INTO users (email_address) VALUES (?)");
        $stmt->execute(array($email))
        $email_id = $db->lastInsertId();
    } else {
        $row = $stmt->fetch();
        $email_id = $row['email_id'];
        $email_exists = 1;
    }

我使用类似的代码来检查我是否已经有一个游戏列在数据库中。我需要得到游戏的ID号和第三部分的电子邮件地址,这是看用户是否已经有了那个游戏的分数,如果他们已经有了新的分数更高。

if ($email_exists == 0 || $game_exists == 0) {
        // New user or game added to DB - no prior score can exist
        $stmt = $db->prepare("INSERT INTO scores VALUES (?,?,?)");
        $stmt->execute(array($email_id,$game_id,$score));
    } else {
        $stmt = $db->prepare("SELECT * FROM scores WHERE email_id = ? AND game_id = ?");
        $stmt->execute(array($email_id,$game_id));
        if ($stmt->rowCount() == 0) {
            // No previous score for this game
            $stmt = $db->prepare("INSERT INTO scores VALUES (?,?,?)");
            $stmt->execute(array($email_id,$game_id,$score));
        } else {
            // Previous score exists
            $row = $stmt->fetch();
            if ($score > $row['score']) {
                // New score is higher
                $stmt = $db->prepare("UPDATE scores SET score = ? " .
                    . "WHERE email_id = ? AND game_id =?");
                $stmt->execute(array($score, $email_id, $game_id));
                // No action needed if new score is lower
            }
        }
    }

代码看起来工作得很好,但是很慢。另外,在几千条记录之后,它似乎会使脚本超时或什么的。有没有更好的方法来使这个工作?
我试着用Python重新编码它,但是它更慢,而且看起来它甚至没有在数据库中插入行。我几乎不懂Python,这可能没有帮助。
我想创建一个数组并存储需要插入的项,然后一次插入100行或类似的内容,但我需要获取scores连接表的id。我还考虑在数据库中使用UNIQUE约束,并试图找出如何重写插入代码,以使用该约束来防止重复的电子邮件地址或游戏。

idv4meu8

idv4meu81#

创建一个包含参数email,game_id和score的存储过程。让这个过程为你做所有的SQL工作。你的PHP代码将被缩减为一个调用这个过程的循环。结果应该更快,更容易维护:

create procedure sp_add_email_score (
in_email varchar(320),
in_game_id int,
in_score int
)
begin

declare v_email_id int;

select email_id into v_email_id
from users 
where email_address = in_email;

if (v_email_id is null) then
  INSERT INTO users (email_address) VALUES (in_email);
  
  set v_email_id = LAST_INSERT_ID();
end if;

INSERT INTO scores (email_id, game_id, score) 
VALUES(v_email_id, in_game_id, in_score) 
ON DUPLICATE KEY UPDATE score=in_score;

end

如果这个循环仍然太慢,那么是其他东西导致了它的减速。

41zrol4v

41zrol4v2#

这里有很多改进的空间。当涉及到数据库速度时,您的主要目标通常应该是减少对数据库服务器的访问次数。
首先,你要对每一个CSV行执行email-to-id查询,这是没有必要的。最多,你应该对每个用户执行一次并缓存它。更好的是,你可以对整个集合执行一次,将整个集合读入内存数组。类似于:

$stmt = $db->prepare('SELECT email_address, email_id FROM users');
$idMap = array_column($stmt->execute(), 'email_id', 'email_address');

这将为您提供如下数组:

[
    'foo@bar.com' => 1,
    'baz@bar.com' => 2,
]

在脚本开始时执行一次,并在此期间将其保存在内存中。通过此操作,您可以立即查找给定电子邮件的ID。这将删除7999次对数据库的访问。您实际上是在用内存换取CPU和磁盘时间。如果您遇到一封尚未包含在数组中的电子邮件,您可以将其插入并添加到数组中。
接下来,将准备操作移出循环迭代,这将删除至少3 * 7999个数据库匹配项,最多可能删除5 * 7999个匹配项。
接下来,使用fgetcsv()而不是explode(),因为它更简单,并且可以正确处理引用。在执行单个插入之前处理整个CSV。如果你打算丢弃大部分记录,那么创建如此巨大的数据库流量是愚蠢的。因此,首先计算最高值,然后只使用以下内容访问数据库:

$top = [];
$fp = fopen('scores.txt', 'r');
while ([$email, $gameId, $score] = fgetcsv($fp)) {
    if ($score > ($top[$email][$gameId] ?? 0)) {
        $top[$email][$gameId] = $score;
    }
}

给定输入文件:

foo@bar.com,g1,3
foo@bar.com,g1,1
foo@bar.com,g2,2
baz@bar.com,g1,4
baz@bar.com,g2,5
baz@bar.com,g2,6

这将为每个用户生成一个最高分数数组:

Array
(
    [foo@bar.com] => Array
        (
            [g1] => 3
            [g2] => 2
        )

    [baz@bar.com] => Array
        (
            [g1] => 4
            [g2] => 6
        )

)

然后,您可以迭代该数组,并且只基于这些记录执行插入/更新。这将为每个冗余CSV行保存两个查询。

foreach ($top as $email => $scores) {
    foreach ($scores as $gameId => $score) {
        // INSERT INTO scores ($idMap[$email], $gameId, $score)
    }
}

相关问题