我在我的Laravel应用程序中有以下代码:我读取.csv文件中的每一行,并想要更新一个值。但是倍数的更新查询非常慢,.csv 8k 行。我怎样才能加快这段代码?谢谢
DB::beginTransaction();
try {
$delimiter = ",";
$firstLine = true;
if ($handle !== FALSE) {
$position = 1;
while (($csv_line = fgetcsv($handle, 1000, $delimiter)) !== FALSE) {
if ($firstLine == true) {
$firstLine = false;
continue;
}
$player_uid = $csv_line[0];
DB::table('scores')
->where('season_uid', $season_uid)
->where('day', $day)
->where('player_uid', $player_uid)
->update(['position' => $position]);
$position++;
}
fclose($handle);
}
DB::commit();
return true;
} catch (Exception $e) {
Log::error($e);
DB::rollBack();
return false;
}
MySQL不支持批量更新,但是有一个巧妙的技巧可以使用ON DUPLICATE KEY UPDATE 子句将更新替换为插入。这样,您实际上可以批量更新记录。查看此答案以获取一些示例。
据我所知,虽然Laravel在其查询构建器中不支持此子句,因此您必须手动生成查询并通过DB::statement()
发出查询。确保将传入行分块(例如,按 100 分块(,您将看到速度的显着提高。
但请注意,更新 8k 行并不是一项便宜的操作。最佳做法是将其委派给单独的作业,并在应用程序中设置队列,以便您的工作人员可以在后台单独处理这些更新。您可以在官方文档中阅读有关作业和队列的更多信息。
使用单独的作业来执行此操作确实是推荐的方法,但您可以尝试以下代码。创建单个更新查询的想法是在 https://github.com/laravel/ideas/issues/575 上找到的。这家伙减少了加载时间,最终速度提高了~13倍。
请注意,它以前没有经过测试。
DB::beginTransaction();
try {
$csv = array_map('str_getcsv', file('data.csv'));
// remove the first line
array_shift($csv);
// grab only the players uids and their positions
$positions = array_flip(array_column($csv, 0));
array_walk($positions, static function(&$position, $id) {
$position = "WHEN {$id} THEN {$position}";
});
DB::update("UPDATE `scores`
SET `position` = CASE `player_uid` " . implode(' ', $positions) . " END
WHERE `player_uid` in (" . implode(',', array_keys($positions)) . ")
AND `session_uid` = ?
AND `day` = ?", [$season_uid, $day]);
DB::commit();
return true;
} catch (Exception $e) {
Log::error($e);
DB::rollBack();
return false;
}
PS:最好用这种方法写一篇关于性能变化的评论