【问题标题】:UPDATE query form while loop, code is very slowUPDATE查询形式while循环,代码很慢
【发布时间】:2019-06-11 11:22:12
【问题描述】:

我在我的 Laravel 应用程序中有这段代码:我读取了 .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;
        }

【问题讨论】:

  • 我投票结束这个问题,因为它属于 CodeReview
  • .. 但我有根据的猜测是分数表没有正确的索引..SHOW CREATE TABLE scores
  • 从查看代码我敢打赌,性能瓶颈在于SELECTs 的执行,这可能是通过调用DB::table()->where()->... 生成的使用Xdebug profiler 来分析这肯定会更清楚进入这个。
  • “非常慢”有多马赫?你希望它有多快?

标签: php mysql laravel eloquent


【解决方案1】:

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

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

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

【讨论】:

  • “MySQL 不支持批量更新” 我从来没有遇到过这样的情况,因为 MySQL 可以在事务和索引列中处理这样的问题,以用于更新中的 WHERE 子句防止一直扫描表的语句。
  • @RaymondNijland 我的错,应该在那里使用更好的术语 - 我的意思是您不能将多个 UPDATE ... WHERE 查询作为单个查询一起批量处理。从理论上讲,它仍然可以从具有相同 WHERE 子句的所有查询中受益,并因此利用索引来加速更新,而无需将所有开销拆分为单独的查询给您。
  • “我的意思是你不能批量更新多个 UPDATE ... WHERE 查询作为单个查询” 事实上,如果所有更新都在同一张表,那么您可以使用CASE END 语句
  • @RaymondNijland 感谢您的洞察力,不知道这样的结构。我已经更新了我的答案,所以它不会让任何人感到困惑。
  • “感谢您的洞察力,不知道这样的结构”没问题,看看this的例子
【解决方案2】:

使用单独的作业确实是推荐的方式,但您可以尝试以下代码。在 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:最好写一篇关于这种方法的性能变化的评论

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-15
    • 2013-03-11
    • 1970-01-01
    • 2013-01-15
    • 2013-03-19
    • 1970-01-01
    相关资源
    最近更新 更多