Speicherleck beim Ausführen der Doctrine-Abfrage in Schleife

Lesezeit: 8 Minuten

Benutzer-Avatar
Jonathan

Ich habe Schwierigkeiten, die Ursache für einen Speicherverlust in meinem Skript zu finden. Ich habe eine einfache Repository-Methode, die eine ‘count’-Spalte in meiner Entität um den Betrag X erhöht:

public function incrementCount($id, $amount)
{
    $query = $this
        ->createQueryBuilder('e')
        ->update('MyEntity', 'e')
        ->set('e.count', 'e.count + :amount')
        ->where('e.id = :id')
        ->setParameter('id', $id)
        ->setParameter('amount', $amount)
        ->getQuery();

    $query->execute();
}

Das Problem ist, wenn ich dies in einer Schleife aufrufe, steigt die Speichernutzung bei jeder Iteration:

$entityManager = $this->getContainer()->get('doctrine')->getManager();
$myRepository = $entityManager->getRepository(MyEntity::class);
while (true) {
    $myRepository->incrementCount("123", 5);
    $doctrineManager->clear();
    gc_collect_cycles();
}

Was fehlt mir hier? ich habe es versucht ->clear()gemäß der Lehre Beratung zur Stapelverarbeitung. Ich habe es sogar versucht gc_collect_cycles()aber das Problem bleibt.

Ich verwende Doctrine 2.4.6 auf PHP 5.5.

  • Versuchen Sie es in Ihrer Funktion als letzte Zeile $query->clear();

    – Mihai

    28. Oktober 2014 um 19:36 Uhr


  • Sie werden auf unbestimmte Zeit mit while(true) schleifen; Gibt es irgendwo eine Pause, einen Rückweg oder einen Ausgang?

    – Halayem Anis

    28. Oktober 2014 um 19:39 Uhr

  • @Mihai Das gibt nur a Call to undefined method Doctrine\ORM\Query::clear() Error.

    – Jonathan

    28. Oktober 2014 um 22:16 Uhr

  • @HalayemAnis ja, ich habe es gerade aus dem Beispiel entfernt, um meine Frage einfach zu halten.

    – Jonathan

    28. Oktober 2014 um 22:18 Uhr

  • Ich denke, Sie können Ihr Problem lösen, indem Sie eine dauerhafte Verbindung zu Ihrer Datenbank konfigurieren

    – Halayem Anis

    28. Oktober 2014 um 22:38 Uhr

Benutzer-Avatar
Collin Krawll

Ich bin gerade auf das gleiche Problem gestoßen, dies sind die Dinge, die es für mich behoben haben:

–kein Debug

Wie das OP in seiner Antwort erwähnte, Einstellung --no-debug (Ex: php bin/console <my_command> --no-debug) ist entscheidend für Leistung/Speicher in Symfony-Konsolenbefehlen. Dies gilt insbesondere bei der Verwendung von Doctrine, da Doctrine ohne Doctrine in den Debug-Modus wechselt, der eine enorme Menge an zusätzlichem Speicher verbraucht (der bei jeder Iteration zunimmt). Siehe die Symfony-Dokumentation hier und hier Für mehr Information.

–env=prod

Sie sollten auch immer die Umgebung angeben. Standardmäßig verwendet Symfony die dev Umgebung für Konsolenbefehle. Das dev Die Umgebung ist normalerweise nicht für Speicher, Geschwindigkeit, CPU usw. optimiert. Wenn Sie über Tausende von Elementen iterieren möchten, sollten Sie wahrscheinlich die verwenden prod Umfeld (z.B.: php bin/console <my_command> --env prod). Sehen hier und hier Für mehr Information.

Tipp: Ich habe eine Umgebung namens erstellt console die ich speziell für die Ausführung von Konsolenbefehlen konfiguriert habe. Hier gibt es Infos dazu wie man zusätzliche Symfony-Umgebungen erstellt.

php -d memory_limit=YOUR_LIMIT

Wenn Sie ein großes Update ausführen, sollten Sie wahrscheinlich auswählen, wie viel Speicher für den Verbrauch akzeptabel ist. Dies ist besonders wichtig, wenn Sie glauben, dass ein Leck vorliegen könnte. Sie können den Speicher für den Befehl angeben, indem Sie verwenden php -d memory_limit=x (Ex: php -d memory_limit=256M). Hinweis: Sie können das Limit auf festlegen -1 (normalerweise die Standardeinstellung für die PHP-CLI), den Befehl ohne Speicherbegrenzung laufen zu lassen, aber das ist offensichtlich gefährlich.

Ein wohlgeformter Konsolenbefehl für die Stapelverarbeitung

Ein wohlgeformter Konsolenbefehl zum Ausführen eines großen Updates mit den obigen Tipps würde folgendermaßen aussehen:

php -d memory_limit=256M bin/console <acme>:<your_command> --env=prod --no-debug

Verwenden Sie IterableResult von Doctrine

Ein weiteres großes Problem bei der Verwendung von Doctrines ORM in einer Schleife ist die Verwendung von Doctrines IterableResult (siehe die Doctrine Batch Processing-Dokumente). Dies hilft in dem bereitgestellten Beispiel nicht, aber normalerweise geht es bei einer solchen Verarbeitung um die Ergebnisse einer Abfrage.

Regelmäßig spülen

Wenn ein Teil Ihrer Arbeit darin besteht, Daten zu ändern, sollten Sie regelmäßig statt bei jeder Iteration leeren. Spülen ist teuer und langsam. Je seltener Sie spülen, desto schneller wird Ihr Befehl beendet. Denken Sie jedoch daran, dass Doctrine die nicht geleerten Daten im Speicher hält. Je seltener Sie also spülen, desto mehr Speicher benötigen Sie.

Sie können etwa Folgendes verwenden, um alle 100 Iterationen zu leeren:

if ($count % 100 === 0) {
    $this->em->flush();
}

Achten Sie auch darauf, am Ende Ihrer Schleife erneut zu leeren (um die letzten < 100 Einträge zu leeren).

Löschen Sie den EntityManager

Möglicherweise möchten Sie auch nach dem Spülen Folgendes löschen:

    $this->em->flush();
    $em->clear();  // Detach ALL objects from Doctrine.

Oder

    $this->em->flush();
    $em->clear(MyEntity::class); // Detach all MyEntity from Doctrine.
    $em->clear(MyRelatedEntity::class); // Detach all MyRelatedEntity from Doctrine.

Geben Sie die Speichernutzung aus, während Sie gehen

Es kann sehr hilfreich sein, zu verfolgen, wie viel Speicher Ihr Befehl verbraucht, während er ausgeführt wird. Sie können dies tun, indem Sie die Antwort ausgeben, die von PHPs eingebautem zurückgegeben wird memory_get_usage() Funktion.

$output->writeln(memory_get_usage());

Viel Glück!

  • für mich $this->entityManager->getConnection()->getConfiguration()->setSQLLogger(null); das Problem behoben

    – max4ever

    25. Februar 2020 um 14:41 Uhr

Benutzer-Avatar
Jonathan

Ich habe dies durch Hinzufügen gelöst --no-debug zu meinem Befehl. Es stellte sich heraus, dass der Profiler im Debug-Modus Informationen zu jeder einzelnen Abfrage im Speicher gespeichert hat.

  • Jede Doktrinenabfrage erhöhte die Speichernutzung um etwa 4k. Dies hat es für mich behoben.

    – robertstephens

    14. Mai 2015 um 20:00 Uhr

  • Wo genau soll ich die hinzufügen --no-debug? Können Sie bitte spezifizieren?

    Benutzer3599441

    3. Februar 2016 um 15:01 Uhr

  • stackoverflow.com/a/10913115/3757139 zeigt, wie Sie die SQL-Protokollierung programmgesteuert deaktivieren können $em->getConnection()->getConfiguration()->setSQLLogger(null);

    – Samuel

    2. Januar 2017 um 17:20 Uhr

  • ich dachte doctrine.dbal.connections.main.logging: false Die Konfiguration hat denselben Effekt wie --no-debug, aber wie sich herausstellt, ist es das nicht. Ich habe Memory Leak sogar mit logging: false in Doktrin Konfig.

    – Nuryagdy Mustapayev

    2. Mai um 12:38 Uhr

Doctrine protokolliert jede von Ihnen gestellte Anfrage. Wenn Sie viele Abfragen machen (normalerweise in Schleifen), kann Doctrine ein großes Speicherleck verursachen.

Sie müssen den Doctrine SQL Logger deaktivieren, um dies zu umgehen.

Ich empfehle, dies nur für den Schleifenteil zu tun.

Holen Sie sich vor der Schleife den aktuellen Logger:

$sqlLogger = $em->getConnection()->getConfiguration()->getSQLLogger();

Und dann den SQL Logger deaktivieren:

$em->getConnection()->getConfiguration()->setSQLLogger(null);

Schleife hier:
foreach() / while() / for()

Setzen Sie den Logger nach dem Ende der Schleife zurück:

$em->getConnection()->getConfiguration()->setSQLLogger($sqlLogger);

  • Das ist was --no-debug Option tut.

    – KmeCnin

    6. Oktober 2017 um 10:05 Uhr

Für mich war es das Klären der Doktrin, oder wie die Dokumentation sagt, das Loslösen aller Entitäten:

$this->em->clear(); //Here em is the entity manager.

Also innerhalb meiner Schleife alle 1000 Iterationen leeren und alle Entitäten trennen (ich brauche sie nicht mehr):

    foreach ($reader->getRecords() as $position => $value) {
        $this->processValue($value, $position);
        if($position % 1000 === 0){
            $this->em->flush();
            $this->em->clear();
        }
        $this->progress->advance();
    }

Hoffe das hilft.

PS: Hier ist die Dokumentation.

Sie verschwenden Speicher für jede Iteration. Ein viel besserer Weg wäre, die Abfrage vorzubereiten einmal und Argumente tauschen viele Male. Zum Beispiel:

class MyEntity extends EntityRepository{
    private $updateQuery = NULL;

    public function incrementCount($id, $ammount)
    {
        if ( $this->updateQuery == NULL ){
            $this->updateQuery = $this->createQueryBuilder('e')
                ->update('MyEntity', 'e')
                ->set('e.count', 'e.count + :amount')
                ->where('e.id = :id')
                ->getQuery();
        }

        $this->updateQuery->setParameter('id', $id)
                ->setParameter('amount', $amount);
                ->execute();
    }
}

Wie Sie bereits erwähnt haben, können Sie hier die Stapelverarbeitung einsetzen, aber probieren Sie dies zuerst aus und sehen Sie, wie gut sie (wenn überhaupt) funktioniert …

  • Danke für die Antwort, aber das ändert leider nichts am Memory Leak. Ich vermute, das liegt daran, dass die $updateQuery Variable wird bereits vom Garbage Collector bereinigt. Nichtsdestotrotz sollte Ihr Vorschlag die Dinge etwas schneller machen, also werde ich ihn umsetzen. Irgendwelche anderen Ideen?!

    – Jonathan

    28. Oktober 2014 um 22:23 Uhr


  • Das habe ich nicht erwartet. Von wie vielen Iterationen sprechen wir hier?

    – Jovan Perović

    28. Oktober 2014 um 22:49 Uhr

  • Bis zu 10.000 Iterationen.

    – Jonathan

    28. Oktober 2014 um 22:58 Uhr


  • Ok, ich denke, das erfordert eine native Abfrage (über plain Connection Objekt). Siehe dies: stackoverflow.com/questions/3325012/…

    – Jovan Perović

    29. Oktober 2014 um 8:23 Uhr

Benutzer-Avatar
dj_thossi

Ich hatte ähnliche Probleme mit einem Speicherleck. Ich verwende Doctrine in einem Symfony 5.2-Projekt. Genauer gesagt habe ich einen nie endenden Befehl erstellt, der Einträge aus einer Tabelle verarbeitet, Einträge aus einer anderen Tabelle abruft und zwei neue Einträge in anderen Tabellen erstellt. (Ereignisverarbeitung)

Ich habe meine Leckageprobleme in zwei Schritten gelöst.

  1. Ich verwende das --no-debug beim Ausführen des Befehls (wie bereits von Jonathan vorgeschlagen)
  2. Ich habe am Ende der Schleife hinzugefügt $this->entityManager->clear();

Um die Lecks zu sehen und zu identifizieren, habe ich die folgende Zeile verwendet, um die aktuelle Speichernutzung auszugeben:

$output->writeln('Memory Usage in MB: ' . memory_get_usage() / 1024 / 1024);

Vielleicht hilft das jemandem, der immer noch mit Leckagen zu kämpfen hat.

  • Danke für die Antwort, aber das ändert leider nichts am Memory Leak. Ich vermute, das liegt daran, dass die $updateQuery Variable wird bereits vom Garbage Collector bereinigt. Nichtsdestotrotz sollte Ihr Vorschlag die Dinge etwas schneller machen, also werde ich ihn umsetzen. Irgendwelche anderen Ideen?!

    – Jonathan

    28. Oktober 2014 um 22:23 Uhr


  • Das habe ich nicht erwartet. Von wie vielen Iterationen sprechen wir hier?

    – Jovan Perović

    28. Oktober 2014 um 22:49 Uhr

  • Bis zu 10.000 Iterationen.

    – Jonathan

    28. Oktober 2014 um 22:58 Uhr


  • Ok, ich denke, das erfordert eine native Abfrage (über plain Connection Objekt). Siehe dies: stackoverflow.com/questions/3325012/…

    – Jovan Perović

    29. Oktober 2014 um 8:23 Uhr

1014930cookie-checkSpeicherleck beim Ausführen der Doctrine-Abfrage in Schleife

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

Privacy policy