当我执行PDO语句时,内部存储了一个结果集,我可以使用->fetch()
从结果中获取一行。
如果我想将整个结果转换为数组,我可以使用->fetchAll()
。
使用Laravel,在查询生成器文档中,我只有看到一种从执行查询中获得数组结果的方法。
// example query, ~30,000 rows
$scores = DB::table("highscores")
->select("player_id", "score")
->orderBy("score", "desc")
->get();
var_dump($scores);
// array of 30,000 items...
// unbelievable ...
是否有任何方法从查询生成器如PDO会返回的结果集?或者我是否被迫等待查询生成器在返回值之前构建整个数组?
可能是某种->lazyGet()
,或->getCursor()
?
如果是这样的话,我不禁看到查询生成器是一个非常短视的工具。假设有一个查询选择了30,000行。使用PDO,我可以逐行,一次一个->fetch()
,并以很少的额外内存消耗来处理数据。
另一方面,Laravel查询生成器?"内存管理,嗯?"这很好,只需将30,000行加载到一个大数组中!"
PS是的,我知道我可以使用->skip()
和->take()
来抵消和限制结果集。在大多数情况下,这可以很好地工作,因为向用户展示30,000行甚至是不可用的。如果我想生成大型报告,我可以看到PHP很容易耗尽内存。
在@deczo指出一个未记录的函数->chunk()
之后,我在源代码中挖了一下。我发现->chunk()
是一个方便的包装器,可以将我的查询乘以几个查询,但会自动填充->step($m)->take($n)
参数。如果我想构建我自己的迭代器,使用->chunk
和我的数据集,我最终会在我的数据库上查询30,000次而不是1次。
这也没有真正的帮助,因为->chunk()
接受一个回调,这迫使我在构建查询时耦合我的循环逻辑。即使函数是在其他地方定义的,查询也将发生在控制器中,它应该对我的视图或呈现器的复杂性不感兴趣。
进一步挖掘,我发现所有Query Builder查询都不可避免地通过'Illuminate'Database'Connection#run。
// https://github.com/laravel/framework/blob/3d1b38557afe0d09326d0b5a9ff6b5705bc67d29/src/Illuminate/Database/Connection.php#L262-L284
/**
* Run a select statement against the database.
*
* @param string $query
* @param array $bindings
* @return array
*/
public function select($query, $bindings = array())
{
return $this->run($query, $bindings, function($me, $query, $bindings)
{
if ($me->pretending()) return array();
// For select statements, we'll simply execute the query and return an array
// of the database result set. Each element in the array will be a single
// row from the database table, and will either be an array or objects.
$statement = $me->getReadPdo()->prepare($query);
$statement->execute($me->prepareBindings($bindings));
return $statement->fetchAll($me->getFetchMode());
});
}
看到底部那个讨厌的$statement->fetchAll
了吗?
这意味着数组用于所有人,总是和永远;你的愿望和梦想被抽象成一个无用的工具 Laravel Query Builder。
我无法表达我现在有多沮丧
我要说的是,Laravel的源代码至少组织得很好,格式也很好。现在让我们加入一些好的代码!
使用chunk
:
DB::table('highscores')
->select(...)
->orderBy(...)
->chunk($rowsNumber, function ($portion) {
foreach ($portion as $row) { // do whatever you like }
});
显然返回的结果将与调用get
相同,所以:
$portion; // array of stdObjects
// and for Eloquent models:
Model::chunk(100, function ($portion) {
$portion; // Collection of Models
});
这是一种使用laravel查询生成器进行查询的方法,但是要使用底层的pdo fetch来循环遍历记录集,我相信这将解决您的问题-运行一个查询并循环记录集,这样您就不会在30k记录上耗尽内存。
这种方法将使用你在laravel中设置的所有配置内容,所以你不必单独配置pdo。
您还可以抽象出一个方法,使其易于使用,该方法接受查询构建器对象,并返回记录集(执行pdo语句),然后将其while循环,如下所示
$qb = DB::table("highscores")
->select("player_id", "score")
->orderBy("score", "desc");
$connection = $qb->getConnection();
$pdo = $connection->getPdo();
$query = $qb->toSql();
$bindings = $qb->getBindings();
$statement = $pdo->prepare($query);
$statement->execute($bindings);
while ($row = $statement->fetch($connection->getFetchMode()))
{
// do stuff with $row
}