Jaka jest różnica między kursorem laravel a metodą Laravel chunk?

Chciałbym wiedzieć, jaka jest różnica między metodą Laravel chunk a metodą Laravel cursor. Która metoda jest bardziej odpowiednia do zastosowania? Jakie będą przypadki użycia obu z nich? Wiem, że powinieneś użyć kursora, aby zapisać pamięć, ale jak to naprawdę działa w backendzie?

Szczegółowe wyjaśnienie z przykładem byłoby przydatne, ponieważ szukałem na stackoverflow i innych stronach, ale nie znalazłem zbyt wiele informacji.

Oto fragment kodu z dokumentacja laravel.

Chunking Results

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

Używanie Kursorów

foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
    //
}
Author: Community, 2017-08-02

6 answers

Rzeczywiście to pytanie może przyciągnąć pewną opiniotwórczą odpowiedź, jednak prosta odpowiedź jest tutaj w Laravel Docs

Tylko dla odniesienia:

To jest chunk:

Jeśli potrzebujesz przetworzyć tysiące elokwentnych rekordów, użyj polecenia chunk. Metoda chunk pobierze "fragment" wymownych modeli, przekazując je do danego Closure w celu przetworzenia. Użycie metody chunk pozwoli zaoszczędzić pamięć podczas pracy z dużymi zestawami wyników:

To jest Kursor:

Metoda cursor pozwala na iterację rekordów bazy danych za pomocą kursora, który wykona tylko jedno zapytanie. Przy przetwarzaniu dużych ilości danych można użyć metody cursor, aby znacznie zmniejszyć zużycie pamięci:

Chunk pobiera rekordy z bazy danych i ładuje je do pamięci, ustawiając kursor na ostatnim pobranym rekordzie, aby nie doszło do kolizji.

Więc zaletą jest tutaj, jeśli chcesz sformatować large rekord zanim zostaną wysłane, lub chcesz wykonać operację na n-tej liczbie rekordów na raz, to jest to przydatne. Przykładem jest budowanie arkusza view out / excel, dzięki czemu można wziąć rekord w liczbach, dopóki nie zostaną wykonane, aby wszystkie z nich nie zostały załadowane do pamięci na raz, a tym samym osiągnięcie limitu pamięci.

Kursor używa generatorów PHP, możesz sprawdzić stronę Generatory php jednak tutaj jest ciekawy podpis:

Generator pozwala na pisanie kodu, który wykorzystuje foreach do iteracji zbioru danych bez konieczności budowania tablicy w pamięci, co może spowodować przekroczenie limitu pamięci lub wymagać znacznego czasu przetwarzania do wygenerowania. Zamiast tego, możesz napisać funkcję generatora, która jest taka sama jak normalna funkcja, z tym wyjątkiem, że zamiast zwróćING raz, generator może uzyskać tyle razy, ile musi, aby dostarczyć wartości do be iterated over.

Chociaż nie mogę zagwarantować, że w pełni rozumiem pojęcie kursora, ale w przypadku Chunk, chunk uruchamia zapytanie przy każdym rozmiarze rekordu, pobierając je i przekazując do zamknięcia w celu dalszych prac nad rekordami.

Mam nadzieję, że to się przyda.
 28
Author: Oluwatobi Samuel Omisakin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-26 00:08:07

Mamy porównanie: chunk () vs cursor()

  • cursor (): High Speed
  • chunk (): stałe zużycie pamięci

10,000 rekordy :

+-------------+-----------+------------+
|             | Time(sec) | Memory(MB) |
+-------------+-----------+------------+
| get()       |      0.17 |         22 |
| chunk(100)  |      0.38 |         10 |
| chunk(1000) |      0.17 |         12 |
| cursor()    |      0.16 |         14 |
+-------------+-----------+------------+

100,000 rekordy :

+--------------+------------+------------+
|              | 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: tabela użytkowników domyślnej migracji Laravel
  • Homestead 0.5.0
  • PHP 7.0.12
  • MySQL 5.7.16
  • Laravel 5.3.22
 32
Author: mohammad asghari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-12 05:39:57

Cursor()

  • Tylko Pojedyncze zapytanie
  • Pobierz wynik wywołania PDOStatement::fetch()
  • domyślnie używane jest buforowane zapytanie i pobiera wszystkie wyniki naraz.
  • zamienił tylko bieżący wiersz w wymowny model

Plusy

  • Minimalizuj wymowną pamięć modelu
  • łatwy do manipulowania

Cons

  • ogromny wynik prowadzi do out of memory
  • buforowane lub niebuforowane jest kompromisem

Chunk()

  • chunk query in to queries with limit and offset
  • Pobierz wynik wywołania PDOStatement::fetchAll
  • przekształcił wyniki w wymowne modele

Plusy

  • kontrolowany rozmiar używanej pamięci

Cons

  • przekształcanie wyników w wymowne modele batchly może spowodować nadmiarowość pamięci
  • zapytań i wykorzystania pamięci jest traid-off

TL; DR

Kiedyś myślałem, że cursor () wykona zapytanie za każdym razem i zachowa tylko jeden wynik wiersza w pamięci. Więc kiedy zobaczyłem tabelę porównawczą @ mohammad-asghari, naprawdę się pogubiłem. To musi być jakiś bufor Za kulisami.

Śledząc Kod Laravela jak poniżej

/**
 * 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;
    }
}

Zrozumiałem, że Laravel buduje tę funkcję za pomocą wrap PDOStatement:: fetch(). I przez wyszukiwanie bufor PDO fetch i MySQL, znalazłem ten dokument.

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

Zapytania domyślnie używają trybu buforowanego. Oznacza to, że wyniki zapytań są natychmiast przesyłane z serwera MySQL do PHP, a następnie przechowywane w pamięci procesu PHP.

Więc wykonując PDOStatement:: execute() pobieramy całe wiersze wynikowe na jedynkach i zapisane w pamięci , a nie tylko jeden rząd. Jeśli wynik jest zbyt duży, spowoduje to wyjątek out of memory.

Chociaż pokazany dokument możemy użyć $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);, aby pozbyć się buforowanego zapytania. Ale wadą powinna być ostrożność.

Niezbuforowane zapytania MySQL wykonują zapytanie, a następnie zwracają zasób, podczas gdy dane wciąż czekają na serwerze MySQL na pobranie. To zużywa mniej pamięci po stronie PHP, ale może zwiększyć obciążenie serwera. Chyba że pełny zestaw wyników był pobierane z serwera żadne dalsze zapytania nie mogą być wysyłane przez to samo połączenie. Zapytania niebuforowane mogą być również określane jako "użyj wyniku".

 16
Author: 劉恒溫,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-03 14:55:02

chunk opiera się na paginacji, utrzymuje numer strony i wykonuje pętlę za Ciebie.

Na przykład, DB::table('users')->select('*')->chunk(100, function($e) {}) wykona wiele zapytań, aż zestaw wyników będzie mniejszy niż rozmiar kawałka(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 jest oparty na PDOStatement::fetch i generatorze.

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

Zrobi jedno zapytanie:

select * from `users`

Ale sterownik nie pobiera wyniku ustawionego na raz.

 14
Author: oraoto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-22 08:35:15

Metoda cursor używa leniwych kolekcji, ale uruchamia zapytanie tylko raz.

Https://laravel.com/docs/6.x/collections#lazy-collections

Jednak metoda cursor konstruktora zapytań zwraca instancję LazyCollection. Pozwala to nadal uruchamiać tylko jedno zapytanie z bazą danych, ale także przechowywać tylko jeden wymowny model załadowany w pamięci na raz.

Chunk uruchamia zapytanie wiele razy i ładuje każdy wynik fragmentu do wymownych modeli w jeden raz.

 1
Author: PhillipMcCubbin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-11 10:50:06

Zrobiłem jakiś benchmark używając kursora i gdzie

foreach (\App\Models\Category::where('type','child')->get() as $res){

}

foreach (\App\Models\Category::where('type', 'child')->cursor() as $flight) {
    //
}

return view('welcome');

Oto wynik: chunk jest szybszy dzięki gdzie

 -1
Author: hendra1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-20 11:32:33