Laravel 使用 chunkById 遍历大表

更新日期: 2019-01-25 阅读次数: 2389 分类: Laravel

问题

现在需要定期遍历物流快递表,以更新物流的状态。但是,这个表在线上生产环境可能非常大,一次性取出来遍历可能爆掉服务器内存。

而使用 chunk 方法,会出现漏掉一半数据未处理的情况:

例如,有 A B C D E 五个物流快递单,每次 chunk 取出两条数据。

  • 第一次取出 A B, 处理完之后,A B 标记为已处理
  • 第二次取时,由于 A B 标记为已处理,在取第二页时,直接跳过了 C D,直接取出了 E。
  • C D 就被漏掉了

使用 chunkById

Laravel 5.5 文档中,并没有说明有 chunkById  这个方法;而 Laravel 5.7 中有专门的说明:

If you are updating database records while chunking results, your chunk results could change in unexpected ways. So, when updating records while chunking, it is always best to use the chunkById method instead. This method will automatically paginate the results based on the record's primary key:

DB::table('users')->where('active', false)
    ->chunkById(100, function ($users) {
        foreach ($users as $user) {
            DB::table('users')
                ->where('id', $user->id)
                ->update(['active' => true]);
        }
    });

但是,我在 Laravel 5.5 的代码里还是找到了对应的 chunkById 的实现。

grep chunkById -r vendor/laravel/
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:    public function chunkById($count, callable $callback, $column = null, $alias = null)
vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:    public function chunkById($count, callable $callback, $column = 'id', $alias = null)

        $lastId = 0;

        do {
            $clone = clone $this;

            $results = $clone->forPageAfterId($count, $lastId, $column)->get();

            $countResults = $results->count();

            if ($countResults == 0) {
                break;
            }

            if ($callback($results) === false) {
                return false;
            }

	// 记录了 last id,避免遗漏
            $lastId = $results->last()->{$alias};

            unset($results);
        } while ($countResults == $count);

参考

https://github.com/laravel/framework/issues/23277

爱评论不评论

近期节日

2019年08月23日 处暑
2019年09月03日 抗日胜利纪念日
2019年09月08日 白露
2019年09月08日 国际扫盲日
2019年09月10日 教师节
2019年09月13日 中秋节
2019年09月16日 国际臭氧层保护日
2019年09月16日 世界清洁地球日
2019年09月18日 "九一八"事变纪念日
2019年09月20日 国际爱牙日
2019年09月21日 国际和平日
2019年09月22日 世界无车日
查看更多节日