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

Sur*_*raj 13 php database cursor large-data laravel

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

通过示例的详细解释将是有用的,因为我已经搜索了stackoverflow和其他站点,但我没有找到太多信息.

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

分块结果

Flight::chunk(200, function ($flights) {
    foreach ($flights as $flight) {
        //
    }
});
Run Code Online (Sandbox Code Playgroud)

使用游标

foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
    //
}
Run Code Online (Sandbox Code Playgroud)

劉恒溫*_*劉恒溫 27

Cursor()

  • 只有一个查询
  • 通过调用获取结果 PDOStatement::fetch()
  • 默认情况下使用缓冲查询并一次获取所有结果。
  • 仅将当前行转换为 eloquent 模型

优点

  • 最小化 eloquent 模型内存开销
  • 易于操作

缺点

  • 巨大的结果导致内存不足
  • 缓冲或无缓冲是一种权衡

Chunk()

  • 块查询到具有限制和偏移量的查询
  • 通过调用获取结果 PDOStatement::fetchAll
  • 将结果批量转化为雄辩的模型

优点

  • 可控的已用内存大小

缺点

  • 将结果批量转化为 eloquent 模型可能会导致一些内存开销
  • 查询和内存使用是一种权衡

TL; 博士

我曾经认为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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我了解 Laravel 通过包装PDOStatement::fetch()来构建此功能。通过搜索缓冲区 PDO fetchMySQL,我找到了这个文档。

https://www.php.net/manual/en/mysqlinfo.concepts.buffering.php

查询默认使用缓冲模式。这意味着查询结果会立即从 MySQL 服务器传输到 PHP,然后保存在 PHP 进程的内存中。

因此,通过执行 PDOStatement::execute() 我们实际上以一个为单位获取整个结果行存储在内存中,而不仅仅是一行。所以如果结果太大,就会导致内存不足异常。

虽然显示的文档我们可以$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);用来摆脱缓冲查询。但缺点应该是谨慎。

无缓冲的 MySQL 查询执行查询,然后在数据仍在 MySQL 服务器上等待获取时返回资源。这在 PHP 端使用较少的内存,但会增加服务器的负载。除非从服务器获取完整的结果集,否则无法通过同一连接发送进一步的查询。无缓冲查询也可以称为“使用结果”。


Olu*_*kin 16

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

仅供参考:

这是块:

在此输入图像描述

这是光标:

在此输入图像描述

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

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

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

在此输入图像描述

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

希望这很有用.

  • 如果它可以帮助你更好地理解,Laravel的`select`使用PHP的[`fetchAll`](http://php.net/manual/en/pdostatement.fetchall.php),而Laravel的`cursor`使用PHP的[`fetch`] (http://php.net/manual/en/pdostatement.fetch.php).两者都执行相同数量的SQL,但前者立即使用整个数据构建一个数组,而后者一次获取一行数据,允许仅在内存中保留此行,而不是前一行或后续行. (2认同)

moh*_*ari 13

我们有一个比较: 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 |
+-------------+-----------+------------+
Run Code Online (Sandbox Code Playgroud)

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    |
+--------------+------------+------------+
Run Code Online (Sandbox Code Playgroud)
  • TestData:Laravel默认迁移的用户表
  • Homestead 0.5.0
  • PHP 7.0.12
  • MySQL 5.7.16
  • Laravel 5.3.22

  • @IonBazan 是的。但这对于数据库游标来说是非常意外的行为。原因是 Laravel 将底层 PDO 连接配置为类似的行为。 (3认同)
  • 您是否知道为什么块的内存使用率比游标低?我觉得这有点奇怪。 (2认同)
  • @AnttiPihlaja我认为这是因为 `cursor()` 仍然将结果集(100k 记录)保留在内存中,并按需将行作为对象获取(使用 `PDOStatement::fetch`。`chunk()` 使用 `LIMIT` 和“OFFSET”用于限制结果集大小,并使用“PDOStatement::fetchAll”将每个块/查询(10k 行)的整个结果集加载到内存中。 (2认同)

小智 9

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;
...
Run Code Online (Sandbox Code Playgroud)

cursor基于PDOStatement::fetch和Generator.

$cursor = DB::table('users')->select('*')->cursor()
foreach ($cursor as $e) { }
Run Code Online (Sandbox Code Playgroud)

会发出一个查询:

select * from `users`
Run Code Online (Sandbox Code Playgroud)

但是驱动程序不会立即获取结果集.