UPDATE查询形式while循环,代码很慢

时间:2019-06-11 11:22:12

标签: php mysql laravel eloquent

我在Laravel App中有以下代码:我读取.csv文件中的每一行,并希望更新值。但是,倍数的更新查询使用8k行的.csv时非常慢。我如何加快此代码的速度?谢谢

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;
        }

2 个答案:

答案 0 :(得分:0)

MySQL不支持批量更新,但是有一个巧妙的技巧可以使用ON DUPLICATE KEY UPDATE子句用插入替换更新。这样,您实际上可以批量更新记录。看看这个答案for some examples

据我所知,尽管Laravel在其查询生成器中不支持此子句,所以您将必须手动生成查询并通过DB::statement()发出查询。确保对传入的行进行分块(例如,减少100条),您将看到速度显着提高。

但是要意识到,更新8k行并不是一个便宜的操作。最佳实践是将其委派给单独的作业,并在应用程序中设置队列,以便工作人员可以在后台分别处理这些更新。您可以了解有关作业和队列in the official documentation的更多信息。

答案 1 :(得分:0)

确实建议使用单独的作业来执行此操作,但是您可以尝试以下代码。在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:最好用这种方法写一篇有关性能变化的评论