chunk在计算机行业通常表示数据块。数据库查询下也有一个chunk方法,依据函数式编程思想设计。第一个参数是chunk大小,第二个参数是一个callable。
使用方法如下:
DB::table('users')->orderBy('id')->chunk(100, function ($users) {
foreach ($users as $user) {
//
}
});
它的实现方式其实不高深但是用于批量传输数据的时候非常实用。代码实现在Illuminate\Database\Concerns\BuildsQueries (Laravel 5.4)。copy如下:
/**
* Chunk the results of the query.
*
* @param int $count
* @param callable $callback
* @return bool
*/
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) === false) {
return false;
}
$page++;
} while ($countResults == $count);
return true;
}
可以看到其实就是一个分页查询。因此它也具备分页查询的缺点:在超长列表下查询性能差。比如100w的列表,查询到后面会出现Limit 990000, 100这样的分页查询,对于超大的偏移量,MySQL的处理是从头扫描!!!因此,分页越往后单个查询耗时越多。
上图是一个数据库QPS(每秒查询数)记录,15:00~17:00有三段下坡的曲线就是执行上面代码产生的(三次运行,使用不同limit,所以基数不同),充分阐释了offset带来的性能问题。每一次执行之初具有极快的返回时间,所以每秒可以发送更多的请求。随着offset增大,每次查询耗时越来越多,导致每秒可返回的查询数不断减少。
这种情况可以结合实际情况对查询条件进行优化,因为上面查询是主键扫描,所以可以用主键做分段条件,类似上面那个查询我是这样做的:
$start;//起始ID
$end;//终止ID
$intervalLen = 10000; //区间长度,SQL的最大offset
$intervalL = $start; //左区间
do{
$intervalR = min($intervalL + $intervalLen, $end); //右区间
DB::table('users')
->where('id', '>=', $intervalL)
->where('id', '<', $intervalR)
->orderBy('id', 'ASC')
->chunk(200, function($records) use ($otsClient){
$put_rows = [];
foreach ($records as $record) {
......
}
});
$intervalL = $intervalR;
}while($intervalL < $end);
因为这个查询是根据id升序排序查询,所以将超长列表根据id再分区间。避免过大的offset。而条件中的id因为是主键,所以可以利用索引快速定位。因此可以一定程度上提高效率。上图17:00之后的统计曲线就是优化之后的代码产生的,可以看到已经没有了衰减的趋势(个别位置凹下去是因为跑完了$start~$end的所有数据,手动重启的间隙)。
总结一下,这种批量处理数据避免过大的offset是性能提升的关键,其次在内存、写入等条件允许的情况下提高limit也有一定好处。其实上面例子中区间长度如果与chunk相等是最优效果。可是这样的话有什么好chunk呢![]()

