Laravel chunk分块处理数据 以及存在的一些坑
假设这样的情况: 您有一个大的数据库表(比如10000行或更大的行),并且需要对一列进行更新。但是不只是运行SQL查询,还有很多PHP逻辑。 因此foreach循环可能会占用很多的运行时间,或者遇到默认的30秒脚本限制。下面说说Laravel的解决方案.
例如:
$users = User::all();
foreach ($users as $user) {
$some_value = ($user->some_field > 0) ? 1 : 0;
// might be more logic here
$user->update(['some_other_field' => $some_value]);
}
对于有100或500个用户的DB表来说,这是很好的解决方式。但是一万呢?十万?百万?这不仅与速度有关,还与存储数据有关—您可能将整个数据库表存储到变量中,可能会耗尽内存。
laravel中: 模型的 chunk() 方法将数据分割成类似分页的形式去解决这个问题.
例如:
User::chunk(100, function ($users) {
foreach ($users as $user) {
$some_value = ($user->some_field > 0) ? 1 : 0;
// might be more logic here
$user->update(['some_other_field' => $some_value]);
}
});
通过循环,选择100个条目,然后用它们进行更新,然后再选择100个条目,再进行一次更新等等。这意味着在任何情况下都不会有大量的数据从数据库中获取——您处理的是一大块条目,而不是整个表。
但laravel 的chunk()存在以下的坑,避免处理以下的操作:
User::where('approved', 0)->chunk(100, function ($users) {
foreach ($users as $user) {
$user->update(['approved' => 1]);
}
});
原因:如您正在筛选未经批准的用户,然后批准他们,在下一个块上,当Laravel正在执行另一个数据库查询以获取另一 页 数据时,数据已经在那时更改了,您将丢失一页数据。这意味着你将只处理一半的条目。
所以不要更新会影响初始筛选条件的字段。
注意:
laravel chunk操作的方式虽好,但使用的时候注意有个弊端(update时候):他每次操作的是一个数据块而不是整个数据库,会漏掉一些数据,目前好像并没有解决办法,所以用的时候得注意;
laravel chunk原代码如下
public function chunk($count, callable $callback)
{
$this->enforceOrderBy();
$page = 1;
do {
// We'll execute the query for the given page and get the results. If there are
// no results we can just break and return from here. When there are results
// we will call the callback with the current chunk of these results here.
$results = $this->forPage($page, $count)->get();
$countResults = $results->count();
if ($countResults == 0) {
break;
}
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
if ($callback($results, $page) === false) {
return false;
}
unset($results);
$page++;
} while ($countResults == $count);
return true;
}