我想知道laravel chunk和laravel cursor方法之间有什么区别。哪种方法更适合使用?两者的用例是什么?我知道你应该使用游标来节省内存,但它在后端实际上是如何工作的?
使用示例的详细解释会很有用,因为我已经在stackoverflow和其他网站上搜索过但我没有找到太多信息。
以下是laravel文档中的代码片段。
分组结果
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
使用游标
foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
//
}
答案 0 :(得分:14)
确实这个问题可能会吸引一些自以为是的答案,但简单的答案就在Laravel Docs
仅供参考:
这是块:
这是Cursor:
Chunk从数据库中检索记录,并将光标加载到内存中,同时将光标设置在检索到的最后一条记录上,这样就不存在冲突。
因此,这里的优势在于,如果您希望在发送大记录之前重新格式化,或者您希望每次对第n个记录执行操作,那么这很有用。例如,如果您正在构建一个视图输出/ excel表,那么您可以记录计数直到它们完成,以便所有这些记录不会立即加载到内存中,从而达到内存限制。
Cursor使用PHP生成器,您可以查看php generators页面,但这里有一个有趣的标题:
虽然我不能保证我完全理解Cursor的概念,但是对于Chunk,chunk在每个记录大小运行查询,检索它,并将其传递到闭包中以进一步处理记录。
希望这很有用。
答案 1 :(得分:10)
我们有一个比较: chunk() vs cursor()
10,000条记录:
+-------------+-----------+------------+
| | Time(sec) | Memory(MB) |
+-------------+-----------+------------+
| get() | 0.17 | 22 |
| chunk(100) | 0.38 | 10 |
| chunk(1000) | 0.17 | 12 |
| cursor() | 0.16 | 14 |
+-------------+-----------+------------+
100,000条记录:
+--------------+------------+------------+
| | Time(sec) | Memory(MB) |
+--------------+------------+------------+
| get() | 0.8 | 132 |
| chunk(100) | 19.9 | 10 |
| chunk(1000) | 2.3 | 12 |
| chunk(10000) | 1.1 | 34 |
| cursor() | 0.5 | 45 |
+--------------+------------+------------+
答案 2 :(得分:8)
chunk
基于分页,它维护页码,并为您循环。
例如,DB::table('users')->select('*')->chunk(100, function($e) {})
将执行多个查询,直到结果集小于块大小(100
):
select * from `users` limit 100 offset 0;
select * from `users` limit 100 offset 100;
select * from `users` limit 100 offset 200;
select * from `users` limit 100 offset 300;
select * from `users` limit 100 offset 400;
...
cursor
基于PDOStatement::fetch
和Generator。
$cursor = DB::table('users')->select('*')->cursor()
foreach ($cursor as $e) { }
会发出一个查询:
select * from `users`
但是驱动程序不会立即获取结果集。
答案 3 :(得分:5)
Cursor()
PDOStatement::fetch()
专业人士
缺点
Chunk()
PDOStatement::fetchAll
专业人士
缺点
TL; DR
我曾经认为 cursor()每次都会查询,并且只在内存中保留一行结果。因此,当我看到@ mohammad-asghari的比较表时,我感到非常困惑。它必须在幕后缓冲区。
通过跟踪以下Laravel代码
/**
* Run a select statement against the database and returns a generator.
*
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @return \Generator
*/
public function cursor($query, $bindings = [], $useReadPdo = true)
{
$statement = $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
if ($this->pretending()) {
return [];
}
// First we will create a statement for the query. Then, we will set the fetch
// mode and prepare the bindings for the query. Once that's done we will be
// ready to execute the query against the database and return the cursor.
$statement = $this->prepared($this->getPdoForSelect($useReadPdo)
->prepare($query));
$this->bindValues(
$statement, $this->prepareBindings($bindings)
);
// Next, we'll execute the query against the database and return the statement
// so we can return the cursor. The cursor will use a PHP generator to give
// back one row at a time without using a bunch of memory to render them.
$statement->execute();
return $statement;
});
while ($record = $statement->fetch()) {
yield $record;
}
}
我了解Laravel通过包装 PDOStatement :: fetch()构建了此功能。 通过搜索 缓冲区PDO获取和 MySQL ,我找到了此文档。
https://www.php.net/manual/en/mysqlinfo.concepts.buffering.php
默认情况下,查询使用的是缓冲模式。这意味着查询结果会立即从MySQL服务器传输到PHP,然后保存在PHP进程的内存中。
因此,通过执行PDOStatement :: execute(),我们实际上将整个结果行取为1,并存储在内存中,而不仅仅是一行。因此,如果结果太大,则会导致内存不足异常。
尽管显示的文档我们可以使用$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
来摆脱缓冲查询。但是缺点是要谨慎。
无缓冲的MySQL查询执行查询,然后在数据仍在MySQL服务器上等待获取的同时返回资源。这样会在PHP端使用较少的内存,但会增加服务器的负载。除非从服务器获取了完整的结果集,否则无法通过同一连接发送进一步的查询。未缓冲的查询也可以称为“使用结果”。
答案 4 :(得分:1)
cursor方法使用惰性集合,但是只运行一次查询。
https://laravel.com/docs/6.x/collections#lazy-collections
但是,查询生成器的cursor方法返回LazyCollection实例。这样一来,您仍然可以只对数据库运行单个查询,还可以一次仅将一个Eloquent模型加载到内存中。
Chunk多次运行查询,并将块的每个结果一次加载到Eloquent模型中。
答案 5 :(得分:0)
我使用游标和
进行了一些基准测试foreach (\App\Models\Category::where('type','child')->get() as $res){
}
foreach (\App\Models\Category::where('type', 'child')->cursor() as $flight) {
//
}
return view('welcome');
答案 6 :(得分:0)
假设您在 db 中有 100 万条记录。 可能这会给出最好的结果。 你可以使用类似的东西。有了它,您将使用分块的 LazyCollections。
{{1}}