Fuite de mémoire lors de L'exécution D'une requête de Doctrine en boucle

j'ai du mal à trouver la cause d'une fuite de mémoire dans mon script. J'ai une méthode de dépôt simple qui incrémente une colonne 'count' dans mon entity par X montant:

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();
}

problème est, si je l'appelle dans une boucle l'utilisation de la mémoire ballons sur chaque itération:

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

Qu'est-ce que je rate ici? J'ai essayé ->clear() , comme selon la Doctrine conseil sur le traitement par lots . J'ai même essayé gc_collect_cycles() , mais la question demeure.

je lance la Doctrine 2.4.6 sur PHP 5.5.

5
demandé sur Jonathan 2014-10-28 22:27:01

5 réponses

j'ai résolu cela en ajoutant --no-debug à mon commandement. Il s'avère que dans le mode de débogage, le profileur stockait des informations sur chaque requête dans la mémoire.

11
répondu Jonathan 2015-05-15 08:57:28

vous gaspillez de la mémoire pour chaque itération. Une bien meilleure façon serait de préparer la requête une fois et d'échanger des arguments plusieurs fois . Par exemple:

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();
    }
}

Comme vous l'avez mentionné, vous pouvez utiliser le traitement par lot ici, mais essayez d'abord et voir comment bien (si) effectue...

4
répondu Jovan Perovic 2014-10-28 19:42:05

je viens de tomber sur le MÊME PROBLÈME, Ce sont les choses qui l'ont réparé pour moi:

--no-debug

comme L'OP l'a mentionné dans sa réponse, le réglage --no-debug (ex: php app/console <my_command> --no-debug ) est crucial pour la performance/mémoire dans les commandes de console Symfony. Cela est particulièrement vrai lorsque L'on utilise la Doctrine, car sans elle, la Doctrine passera en mode de débogage qui consomme une énorme quantité de mémoire supplémentaire (qui augmente à chaque itération). Voir la Symfony docs ici et ici pour plus d'information.

--env =prod

Vous devez toujours spécifier l'environnement. Par défaut, Symfony utilise l'environnement dev pour les commandes de la console. L'environnement dev n'est généralement pas optimisé pour la mémoire, la vitesse, le cpu, etc. Si vous voulez itérer des milliers d'éléments, vous devriez probablement utiliser l'environnement prod (ex: php app/console <my_command> --no-debug ). Voir le ici et ici pour plus d'info.

astuce: j'ai créé un environnement appelé console que j'ai spécialement configuré pour exécuter des commandes de console. Voici des informations sur comment créer des environnements Symfony supplémentaires .

php-d memory_limit=YOUR_LIMIT

si vous exécutez une grosse mise à jour, vous devriez probablement choisir combien de mémoire est acceptable pour qu'elle consomme. Ceci est particulièrement important si vous pensez qu'il pourrait être une fuite. Vous pouvez spécifier la mémoire de la commande en utilisant php -d memory_limit=x (ex: php -d memory_limit=256M ). Note: vous pouvez définir la limite à -1 (généralement la valeur par défaut pour le CLI php) pour laisser la commande tourner sans limite de mémoire, mais c'est évidemment dangereux.

Bien Formés De Commande De La Console Pour Le Traitement Par Lot

une commande de console bien formée pour exécuter une grande mise à jour en utilisant les conseils ci-dessus ressemblerait à:

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

utiliser le résultat Itérablede la Doctrine

un autre énorme, lorsque l'on utilise L'ORM de Doctrine dans une boucle, est d'utiliser le résultat Itérableresult de doctrine (voir le Doctrine lot Processing docs ). Cela n'aidera pas dans l'exemple fourni, mais habituellement quand vous faites un traitement comme celui-ci, il est sur les résultats d'une requête.

sortie de la mémoire l'utilisation que vous allez.

il peut être vraiment utile de garder une trace de combien de mémoire votre commande consomme pendant qu'elle tourne. Vous pouvez faire cela en dépassant la réponse retournée par la fonction PHP construite dans memory_get_usage() .

bonne chance!

4
répondu Collin Krawll 2016-12-08 16:47:20

Doctrine conserve les journaux de toute requête que vous faites. Si vous faites beaucoup de requêtes (se produit normalement dans les boucles) Doctrine peut causer une fuite de mémoire énorme.

vous devez désactiver la Doctrine SQL Logger pour surmonter cela.

je recommande de ne le faire que pour la partie boucle.

Avant Boucle, obtenir logger courant:

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

puis désactiver le logger SQL:

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

boucle ici: foreach() / while() / for()

après les extrémités de boucle, remettre l'enregistreur:

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

4
répondu mFlorin 2017-05-11 12:57:15

pour moi, c'était la doctrine d'affranchissement, ou comme le dit la documentation, détachant toutes les entités:

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

donc à l'intérieur de ma boucle y rincer toutes les itérations 1000 et détacher toutes les entités (Je n'en ai plus besoin):

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

Espérons que cette aide.

PS: voici la documentation .

1
répondu amcastror 2017-11-25 17:27:47