laravel游标和laravel chunk方法有什么区别?

时间:2017-08-02 15:12:39

标签: php database laravel cursor large-data

我想知道laravel chunk和laravel cursor方法之间有什么区别。哪种方法更适合使用?两者的用例是什么?我知道你应该使用游标来节省内存,但它在后端实际上是如何工作的?

使用示例的详细解释会很有用,因为我已经在stackoverflow和其他网站上搜索过但我没有找到太多信息。

以下是laravel文档中的代码片段。

分组结果

Flight::chunk(200, function ($flights) {
    foreach ($flights as $flight) {
        //
    }
});

使用游标

foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
    //
}

7 个答案:

答案 0 :(得分:14)

确实这个问题可能会吸引一些自以为是的答案,但简单的答案就在Laravel Docs

仅供参考:

这是块:

enter image description here

这是Cursor:

enter image description here

Chunk从数据库中检索记录,并将光标加载到内存中,同时将光标设置在检索到的最后一条记录上,这样就不存在冲突。

因此,这里的优势在于,如果您希望在发送记录之前重新格式化,或者您希望每次对第n个记录执行操作,那么这很有用。例如,如果您正在构建一个视图输出/ excel表,那么您可以记录计数直到它们完成,以便所有这些记录不会立即加载到内存中,从而达到内存限制。

Cursor使用PHP生成器,您可以查看php generators页面,但这里有一个有趣的标题:

enter image description here

虽然我不能保证我完全理解Cursor的概念,但是对于Chunk,chunk在每个记录大小运行查询,检索它,并将其传递到闭包中以进一步处理记录。

希望这很有用。

答案 1 :(得分:10)

我们有一个比较: chunk() vs cursor()

  • cursor():高速
  • chunk():恒定内存使用量

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    |
+--------------+------------+------------+
  • TestData:Laravel默认迁移的用户表
  • 宅基地0.5.0
  • PHP 7.0.12
  • MySQL 5.7.16
  • Laravel 5.3.22

答案 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');

结果如下: chunk is faster thank using where

答案 6 :(得分:0)

假设您在 db 中有 100 万条记录。 可能这会给出最好的结果。 你可以使用类似的东西。有了它,您将使用分块的 LazyCollections。

{{1}}