В чем разница между курсом laravel и методом laravel chunk?

Я хотел бы знать, в чем разница между larvel chunk и larvel курсором. Какой метод более подходит для использования? Каковы будут варианты использования для обоих из них? Я знаю, что вы должны использовать курсор для сохранения памяти, но как она работает на бэкэнд?

Подробное объяснение с примером было бы полезно, потому что я искал в stackoverflow и других сайтах, но я не нашел много информации.

Вот фрагмент кода из документации laravel.

Результаты квантования

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

Использование курсоров

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

Ответ 1

На самом деле, этот вопрос может привлечь какой-то самоуверенный ответ, однако простой ответ здесь, в Документах Laravel

Просто для справки:

Это кусок:

enter image description here

Это курсор:

enter image description here

Chunk извлекает записи из базы данных и загружает их в память, устанавливая курсор на последнюю извлеченную запись, чтобы избежать конфликта.

Таким образом, преимущество здесь в том, что если вы хотите переформатировать большую запись перед их отправкой, или вы хотите выполнить операцию с n-м числом записей за раз, тогда это полезно. Например, если вы создаете лист вида out/excel, то есть вы можете считать записи в счетчиках до тех пор, пока они не будут выполнены, чтобы все они не загружались в память сразу и, таким образом, превышали лимит памяти.

Курсор использует PHP Generators, вы можете проверить страницу генераторов php, однако вот интересная подпись:

enter image description here

Хотя я не могу гарантировать, что я полностью понимаю концепцию Cursor, но для Chunk, chunk выполняет запрос с каждым размером записи, извлекает его и передает его в замыкание для дальнейшей работы с записями.

Надеюсь, это полезно.

Ответ 2

У нас есть сравнение: chunk() и курсор()

  • курсор(): высокая скорость
  • chunk(): постоянное использование памяти

10000 записей:

+-------------+-----------+------------+
|             | 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

Ответ 3

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 и генераторе.

$cursor = DB::table('users')->select('*')->cursor()
foreach ($cursor as $e) { }

выполнит один запрос:

select * from `users`

Но драйвер не получает результат сразу.

Ответ 4

Я сделал несколько тестов, используя курсор и где

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

Ответ 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 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 fetch и MySQL я нашел этот документ.

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

Запросы по умолчанию используют буферный режим. Это означает, что результаты запроса немедленно передаются с MySQL Server на PHP, а затем сохраняются в памяти процесса PHP.

таким образом, выполняя PDOStatement :: execute(), мы фактически извлекаем целые строки результатов по одному и сохраняем в памяти, а не только одну строку. Поэтому, если результат слишком велик, это приведет к исключению нехватки памяти.

Несмотря на то, что документ показан, мы можем использовать $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);, чтобы избавиться от буферизованного запроса. Но недостатком следует быть осторожным.

Небуферизованные запросы MySQL выполняют запрос и затем возвращают ресурс, пока данные все еще ожидают на сервере MySQL для извлечения. Это использует меньше памяти на стороне PHP, но может увеличить нагрузку на сервер. Если полный набор результатов не был получен с сервера, дальнейшие запросы по тому же соединению не могут быть отправлены. Небуферизованные запросы также могут называться "результатом использования".