Was ist der Unterschied zwischen Laravel-Cursor und Laravel-Chunk-Methode?

Lesezeit: 10 Minuten

Benutzer-Avatar
Suraj

Ich würde gerne wissen, was der Unterschied zwischen der Laravel-Chunk- und der Laravel-Cursor-Methode ist. Welche Methode ist besser geeignet? Was werden die Anwendungsfälle für beide sein? Ich weiß, dass Sie den Cursor verwenden sollten, um Speicher zu sparen, aber wie funktioniert es eigentlich im Backend?

Eine detaillierte Erklärung mit Beispiel wäre nützlich, da ich auf Stackoverflow und anderen Websites gesucht habe, aber nicht viele Informationen gefunden habe.

Hier ist das Code-Snippet aus der Laravel-Dokumentation.

Chunking-Ergebnisse

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

Verwenden von Cursorn

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

  • von dem API-Dokumente: Stück: Chunk der Ergebnisse der Abfrage. Mauszeiger: Holen Sie sich einen Generator für die angegebene Abfrage.

    – Online-Thomas

    2. August 2017 um 15:16 Uhr


  • Schau mal hier ist gut erklärt 🙂

    – Maraboc

    2. August 2017 um 15:19 Uhr

Benutzer-Avatar
seyed mohammad asghari

Wir haben einen Vergleich: chunk() vs. Cursor()

  • Cursor(): Hohe Geschwindigkeit
  • chunk(): Konstante Speichernutzung

10.000 Datensätze:

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

100.000 Datensätze:

+--------------+------------+------------+
|              | 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: Benutzertabelle der Laravel-Standardmigration
  • Gehöft 0.5.0
  • PHP 7.0.12
  • MySQL 5.7.16
  • Laravel 5.3.22

  • Haben Sie eine Ahnung, warum Chunks weniger Speicher verbrauchen als Cursor? Das kommt mir etwas seltsam vor.

    – Antti Pihlaja

    21. Februar 2019 um 11:52 Uhr

  • @AnttiPihlaja Ich denke, das liegt daran cursor() behält die Ergebnismenge (100.000 Datensätze) weiterhin im Speicher und ruft die Zeilen bei Bedarf als Objekte ab (mit PDOStatement::fetch. chunk() Verwendet LIMIT und OFFSET um die Größe der Ergebnismenge zu begrenzen und die gesamte Ergebnismenge für jeden Block/jede Abfrage (10.000 Zeilen) in den Speicher zu laden PDOStatement::fetchAll.

    – Ion Bazan

    31. Juli 2019 um 7:49 Uhr

  • @IonBazan Ja. Aber das ist ein sehr unerwartetes Verhalten für den DB-Cursor. Der Grund dafür ist, dass Laravel die zugrunde liegende PDO-Verbindung so konfiguriert, dass sie sich so verhält.

    – Antti Pihlaja

    2. August 2019 um 3:38 Uhr

  • Es scheint, dass die Verwendung von Cursor immer besser ist als get(), aber das stimmt nicht. Die Cursor-Leistung ist bei größeren Datensätzen langsamer als die von get(), da der Cursor mit fetch Datensätze einzeln aus dem Puffer holt, während get mit fetchAll alles zurückgibt. fetchAll hat sich als schneller als das Schleifen durch fetch erwiesen.

    – Bernhard Wiesner

    18. November 2021 um 5:45 Uhr

  • @BernardWiesner Sie können Ihre Szenarien testen und die Antwort aktualisieren.

    – Seed Mohammad Asghari

    19. November 2021 um 7:48 Uhr

Benutzer-Avatar
Oluwatobi Samuel Omisakin

In der Tat könnte diese Frage eine rechthaberische Antwort hervorrufen, aber die einfache Antwort ist hier drin Laravel-Docs

Nur als Referenz:

Das ist Brocken:

Wenn Sie Tausende von Eloquent-Datensätzen verarbeiten müssen, verwenden Sie die chunk Befehl. Das chunk -Methode ruft einen “Bruch” von Eloquent-Modellen ab und füttert sie mit einem gegebenen Closure zum Bearbeiten. Verwendung der chunk -Methode spart Speicherplatz, wenn mit großen Ergebnismengen gearbeitet wird:

Das ist Cursor:

Das cursor -Methode können Sie Ihre Datenbankeinträge mit einem Cursor durchlaufen, der nur eine einzige Abfrage ausführt. Bei der Verarbeitung großer Datenmengen ist die cursor Methode kann verwendet werden, um Ihre Speichernutzung stark zu reduzieren:

Chunk ruft die Datensätze aus der Datenbank ab und lädt sie in den Arbeitsspeicher, während ein Cursor auf den letzten abgerufenen Datensatz gesetzt wird, damit es keine Konflikte gibt.

Hier ist also der Vorteil, wenn Sie die neu formatieren möchten groß aufnehmen, bevor sie versendet werden, oder wenn Sie eine Operation mit einer n-ten Anzahl von Datensätzen pro Mal durchführen möchten, dann ist dies nützlich. Ein Beispiel ist, wenn Sie ein Ansichts-/Excel-Blatt erstellen, sodass Sie den Datensatz zählen können, bis sie fertig sind, damit nicht alle auf einmal in den Speicher geladen werden und dadurch die Speichergrenze erreicht wird.

Cursor verwendet PHP-Generatoren, die Sie überprüfen können PHP-Generatoren Seite, aber hier ist eine interessante Bildunterschrift:

Mit einem Generator können Sie Code schreiben, der verwendet für jeden um einen Datensatz zu durchlaufen, ohne ein Array im Speicher erstellen zu müssen, was dazu führen kann, dass Sie eine Speichergrenze überschreiten oder eine beträchtliche Menge an Verarbeitungszeit zum Generieren benötigen. Stattdessen können Sie eine Generatorfunktion schreiben, die mit einer normalen identisch ist Funktionaußer dass statt Rückkehring einmal, kann ein Generator Ertrag so oft wie nötig, um die Werte bereitzustellen, über die iteriert werden soll.

Ich kann zwar nicht garantieren, dass ich das Konzept von Cursor vollständig verstehe, aber für Chunk führt Chunk die Abfrage bei jeder Datensatzgröße aus, ruft sie ab und übergibt sie an die Closure für weitere Arbeiten an den Datensätzen.

Hoffe, das ist nützlich.

  • Danke für die ehrliche Antwort. Obwohl ich das Konzept des Cursors immer noch nicht vollständig verstehe. Aber deine Antwort erklärt einiges.

    – Suraj

    2. August 2017 um 16:41 Uhr

  • Wenn es dir helfen kann, es besser zu verstehen, Laravels select verwendet PHPs fetchAll während Laravels cursor verwendet PHPs fetch. Beide führen die gleiche Menge an SQL aus, aber ersteres erstellt sofort ein Array mit den gesamten Daten, während letzteres die Daten zeilenweise abruft, sodass nur diese Zeile im Speicher gehalten werden kann, nicht die vorherige oder die folgenden.

    – Grasdoppelt

    5. Dezember 2018 um 5:28 Uhr

Benutzer-Avatar
劉恒溫

Cursor()

  • nur Einzelabfrage
  • Ergebnis per Anruf abrufen PDOStatement::fetch()
  • standardmäßig wird eine gepufferte Abfrage verwendet und alle Ergebnisse auf einmal abgerufen.
  • verwandelte nur aktuelle Reihe in beredtes Modell

Vorteile

  • Minimieren Sie den Overhead des eloquenten Modellspeichers
  • leicht zu manipulieren

Nachteile

  • riesige Ergebnis führt aus dem Speicher
  • gepuffert oder ungepuffert ist ein Kompromiss

Chunk()

  • Chunk-Abfrage in Abfragen mit Limit und Offset
  • Ergebnis per Anruf abrufen PDOStatement::fetchAll
  • Ergebnisse stapelweise in beredte Modelle verwandelt

Vorteile

  • steuerbare verwendete Speichergröße

Nachteile

  • Wenn Ergebnisse stapelweise in eloquente Modelle umgewandelt werden, kann dies zu einem gewissen Speicheraufwand führen
  • Abfragen und Speicherverbrauch ist ein Kompromiss

TL;DR

Ich dachte immer Mauszeiger() führt jedes Mal eine Abfrage durch und behält nur ein Zeilenergebnis im Speicher. Als ich also die Vergleichstabelle von @mohammad-asghari sah, war ich wirklich verwirrt. Es müssen einige sein Puffer hinter den Kulissen.

Durch Verfolgen des Laravel-Codes wie unten beschrieben

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

Ich habe verstanden, dass Laravel dieses Feature per Wrap erstellt PDOStatement::fetch(). Und per Suche Puffer-PDO-Abruf und MySQLich habe dieses Dokument gefunden.

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

Abfragen verwenden standardmäßig den gepufferten Modus. Das bedeutet, dass Abfrageergebnisse sofort vom MySQL-Server an PHP übertragen und dann im Speicher des PHP-Prozesses gehalten werden.

Indem wir also PDOStatement::execute() ausführen, holen wir tatsächlich ganze Ergebniszeilen bei Einsen und im Speicher abgelegt, nicht nur eine Reihe. Wenn das Ergebnis also zu groß ist, wird dies der Fall sein zu Gedächtnislücken führen Ausnahme.

Obwohl das gezeigte Dokument wir verwenden könnten $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); um die gepufferte Abfrage loszuwerden. Aber der Nachteil sollte Vorsicht sein.

Ungepufferte MySQL-Abfragen führen die Abfrage aus und geben dann eine Ressource zurück, während die Daten noch auf dem MySQL-Server darauf warten, abgerufen zu werden. Dies verbraucht weniger Speicher auf der PHP-Seite, kann aber die Last auf dem Server erhöhen. Wenn nicht die vollständige Ergebnismenge vom Server abgerufen wurde, können keine weiteren Abfragen über dieselbe Verbindung gesendet werden. Ungepufferte Abfragen können auch als “Ergebnis verwenden” bezeichnet werden.

  • sehr schöne erklärung. Ich war verwirrt darüber, wie der Cursor bei einem großen Datensatz zu einem Problem mit nicht genügend Arbeitsspeicher führen würde. Deine Antwort hat mir wirklich weitergeholfen.

    – Anuj Shrestha

    18. April 2020 um 7:17 Uhr

chunk basiert auf Paginierung, behält eine Seitennummer bei und erledigt die Schleifen für Sie.

Zum Beispiel, DB::table('users')->select('*')->chunk(100, function($e) {}) führt mehrere Abfragen durch, bis die Ergebnismenge kleiner als die Blockgröße ist (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 basiert auf PDOStatement::fetch und Generator.

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

wird eine einzige Abfrage ausgeben:

select * from `users`

Aber der Treiber ruft die Ergebnismenge nicht sofort ab.

Die Cursormethode verwendet Lazy Collections, führt die Abfrage jedoch nur einmal aus.

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

Die Cursormethode des Abfragegenerators gibt jedoch eine LazyCollection-Instanz zurück. Auf diese Weise können Sie immer noch nur eine einzige Abfrage für die Datenbank ausführen, aber auch jeweils nur ein Eloquent-Modell im Speicher laden.

Chunk führt die Abfrage mehrmals aus und lädt jedes Ergebnis des Chunks gleichzeitig in Eloquent-Modelle.

Benutzer-Avatar
oguz463

Angenommen, Sie haben einen Millionen-Datensatz in db. Wahrscheinlich würde dies das beste Ergebnis liefern. So etwas kann man verwenden. Damit verwenden Sie Chunked LazyCollections.

User::cursor()->chunk(10000);

Am besten schaut man sich den Quellcode an.

select() oder get()

https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Connection.php#L366

return $statement->fetchAll();

Es verwendet fetchAll die alle Datensätze in den Speicher lädt. Das ist schnell, verbraucht aber viel Speicher.

Mauszeiger()

https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Connection.php#L403

while ($record = $statement->fetch()) {
   yield $record;
}

Es verwendet bringen, lädt es jeweils nur 1 Datensatz aus dem Puffer in den Speicher. Beachten Sie jedoch, dass nur eine Abfrage ausgeführt wird. Weniger Speicher, aber langsamer, da es nacheinander iteriert. (Beachten Sie, dass der Puffer je nach Ihrer PHP-Konfiguration entweder auf der PHP-Seite oder in MySQL gespeichert werden kann. Lesen Sie mehr hier)

Stück ()

https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Concerns/BuildsQueries.php#L30

public function chunk($count, callable $callback)
{
    $this->enforceOrderBy();
    $page = 1;
    do {
        $results = $this->forPage($page, $count)->get();
        $countResults = $results->count();

        if ($countResults == 0) {
            break;
        }

        if ($callback($results, $page) === false) {
            return false;
        }

        unset($results);

        $page++;
    } while ($countResults == $count);

    return true;
}

Verwendet viele kleinere Aufrufe von fetchAll (durch Verwendung von get()) und versucht, den Arbeitsspeicher gering zu halten, indem ein großes Abfrageergebnis in kleinere Abfragen aufgeteilt wird Grenze abhängig von der angegebenen Chunk-Größe. In gewisser Weise wird versucht, den Vorteil von get() und cursor() zu nutzen.

Als Faustregel würde ich sagen, gehen Sie mit Chunk oder noch besser chunkById, wenn Sie können. (Chunk hat eine schlechte Leistung an großen Tischen, da es verwendet wird versetztchunkBy id verwendet Grenze).

faul()

In Laravel 8 gibt es auch lazy(), es ähnelt Chunk, aber die Syntax ist sauberer (verwendet Generatoren)

https://laravel.com/docs/8.x/eloquent#streaming-results-lazily

foreach (Flight::lazy() as $flight) {
    //
}

In macht dasselbe wie chunk(), nur braucht man keinen Callback, da es den PHP-Generator verwendet. Sie können auch lazyById() ähnlich wie chunk verwenden.

1298040cookie-checkWas ist der Unterschied zwischen Laravel-Cursor und Laravel-Chunk-Methode?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy