web-dev-qa-db-fra.com

Quelle est la différence entre la méthode laravel cursor et laravel chunk)?

Je voudrais savoir quelle est la différence entre laravel chunk et laravel méthode du curseur. Quelle méthode est la plus appropriée à utiliser? Quels seront les cas d'utilisation de Je sais que vous devez utiliser le curseur pour économiser de la mémoire, mais comment cela fonctionne-t-il réellement dans le backend?

Une explication détaillée avec exemple serait utile car j'ai cherché sur stackoverflow et d'autres sites mais je n'ai pas trouvé beaucoup d'informations.

Voici l'extrait de code de la documentation laravel.

Résultats de segmentation

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

tilisation de curseurs

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

En effet, cette question pourrait attirer une réponse d'opinion, mais la réponse simple est ici dans Laravel Docs

Juste pour référence:

C'est un morceau:

enter image description here

Voici le curseur:

enter image description here

Chunk récupère les enregistrements de la base de données et le charge en mémoire tout en positionnant un curseur sur le dernier enregistrement récupéré afin qu'il n'y ait pas de conflit.

L'avantage est donc ici si vous souhaitez reformater le grand enregistrement avant qu'il ne soit envoyé, ou si vous souhaitez effectuer une opération sur un nième nombre d'enregistrements par temps, cela est utile. Par exemple, si vous créez une feuille de vue/Excel, vous pouvez prendre l'enregistrement en nombre jusqu'à ce qu'il soit terminé afin que tous ne soient pas chargés en mémoire en même temps et atteignent ainsi la limite de mémoire.

Le curseur utilise PHP Generators, vous pouvez vérifier la page php generators mais voici une légende intéressante:

enter image description here

Bien que je ne puisse pas garantir que je comprends parfaitement le concept de curseur, mais pour Chunk, chunk exécute la requête à chaque taille d'enregistrement, la récupère et la passe à la fermeture pour d'autres travaux sur les enregistrements.

J'espère que c'est utile.

Nous avons une comparaison: chunk () vs cursor ()

  • curseur (): Haute vitesse
  • chunk (): Utilisation constante de la mémoire

10000 enregistrements :

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

100 000 enregistrements :

+--------------+------------+------------+
|              | 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: table des utilisateurs de Laravel migration par défaut
  • Homestead 0.5.0
  • PHP 7.0.12
  • MySQL 5.7.16
  • Laravel 5.3.22
11
mohammad asghari

chunk est basé sur la pagination, il maintient un numéro de page et fait la boucle pour vous.

Par exemple, DB::table('users')->select('*')->chunk(100, function($e) {}) effectuera plusieurs requêtes jusqu'à ce que le jeu de résultats soit inférieur à la taille de bloc (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 est basé sur PDOStatement::fetch et générateur.

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

fera émettre une seule requête:

select * from `users`

Mais le pilote ne récupère pas le jeu de résultats à la fois.

9
oraoto

J'ai fait une référence en utilisant le curseur et où

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

}

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

return view('welcome');

voici le résultat: chunk is faster thank using where

0
hendra1

Cursor()

  • une seule requête
  • récupérer le résultat par appel _ PDOStatement::fetch()
  • par défaut, une requête tamponnée est utilisée et récupère tous les résultats en même temps.
  • transformé uniquement la ligne actuelle en modèle éloquent

Avantages

  • minimiser la surcharge de mémoire du modèle éloquent
  • facile à manipuler

Contre

  • un résultat énorme conduit à une perte de mémoire
  • tamponné ou non tamponné est un compromis

Chunk()

  • bloc de requête dans des requêtes avec limite et décalage
  • récupérer le résultat par appel PDOStatement::fetchAll
  • transformé les résultats en modèles éloquents par lots

Avantages

  • taille de mémoire utilisée contrôlable

Contre

  • transformé les résultats en modèles éloquents par lots peut entraîner une surcharge de mémoire
  • les requêtes et l'utilisation de la mémoire est un traid-off

TL; DR

J'avais l'habitude de penser que cursor () ferait une requête à chaque fois et ne garderait qu'un résultat de ligne en mémoire. Alors, quand j'ai vu le tableau de comparaison de @ mohammad-asghari, je suis devenu vraiment confus. Il doit s'agir d'un tampon dans les coulisses.

En suivant Laravel Code comme ci-dessous

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

J'ai compris Laravel construire cette fonctionnalité par wrap PDOStatement :: fetch () . Et par recherche buffer PDO fetch and MySQL, j'ai trouvé ce document.

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

Les requêtes utilisent le mode tamponné par défaut. Cela signifie que les résultats de la requête sont immédiatement transférés du serveur MySQL vers PHP puis sont conservés dans la mémoire du processus PHP.

donc en faisant PDOStatement :: execute () nous récupérons en fait les lignes de résultats entières à un et stockées dans la mémoire , pas seulement une ligne. Donc, si le résultat est trop énorme, cela entraînera une exception de mémoire insuffisante .

Bien que le document montré, nous pourrions utiliser $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); pour se débarrasser de la requête tamponnée. Mais l'inconvénient doit être la prudence.

Les requêtes MySQL sans tampon exécutent la requête, puis renvoient une ressource pendant que les données attendent toujours sur le serveur MySQL d'être récupérées. Cela utilise moins de mémoire côté PHP, mais peut augmenter la charge sur le serveur. Sauf si l'ensemble de résultats complet a été récupéré à partir du serveur, aucune autre requête ne peut être envoyée via la même connexion. Les requêtes non tamponnées peuvent également être appelées "résultat d'utilisation".

0
劉恒溫