如果你需要处理成千上百条数据库记录,可以考虑使用chunk方法,该方法一次获取结果集的一小块,然后填充每一小块数据到要处理的闭包。使用chunk方法能够在处理大量数据集合时有效减少内存消耗:
假如我们需要批量操作 Topic 数据模型的数据,下面是一个很糟糕的例子:
// 读取 200w 条数据
$topics = Topic::all();
foreach ($topics as $topic) {
$topic->collectImages();
}
一次性同时读取,直接会把内存填满。Laravel 下合理的方式应该是:
Topic::chunk(100, function ($topics) {
foreach ($topics as $topic) {
$topic->collectImages();
}
});
这段代码是执行一个200条的数据进行更新,当执行完成后继续后面的另一百条数据……
也就是说他每次操作的是一个数据块而不是整个数据库。
chunk作用域
chunk内部和外部隔离,可以使用use传入外部变量
$date = 1;
Topic::chunk(100, function ($topics) use($date) {
foreach ($topics as $topic) {
$topic->collectImages();
}
});
原理:
分页的原理:
第一次: select from topic limit 0,100; —从第一条开始,返回100条。返回1-100行
第二次: select from topic limit 100,100; —从第101条开始,返回100条。返回101-200行
优化:
但是chunk查询当数据库很大的时候,也是需要优化的:
当一个查询语句偏移量offset很大的时候,如select * from table limit 100000,10 , 最好不要直接使用limit,而是先获取到offset的id后,再直接使用limit size来获取数据。效果会好很多:
SELECT
*
FROM
voucher
WHERE
id >=(
SELECT
id
FROM
voucher
LIMIT 100000 ,1
)
LIMIT 10;
使用DB查询构建器的方式也可以使用chunk:
DB::table('Broker')->chunk(200, function ($topics) {
foreach ($topics as $topic) {
}
});
可以通过从闭包函数中返回false来中止组块的运行:
DB::table('users')->chunk(100, function($users) {
// 处理结果集...
return false;
});
源码:
<?php
public function chunk($count, callable $callback)
{
$results = $this->forPage($page = 1, $count)->get();
while (count($results) > 0) {
// 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 (call_user_func($callback, $results) === false) {
return false;
}
$page++;
$results = $this->forPage($page, $count)->get();
}
return true;
}
问题,避免数据表的查询与更新的where字段自同一张表
User::where('approved', 0)->chunk(100, function ($users) {
foreach ($users as $user) {
$user->update(['approved' => 1]);
}
});
第一次查询:
select * from users where approved = 0 limit 0,100;
update 这一批数据的 approved 为 1 之后,
再看第二次查询:
select * from users where approved = 0 limit 100,100;
因为上一步已经把前一百条数据的approved更改为0,此时从偏移量100开始查找且approved = 0的条件,所以就漏掉了100条 approved为0的数据
所以,我们要使用 chunk 的时候,避免更改和过滤条件相同的字段的值。