Possible mem leak in Query::getSingleScalarResult #6200

Open
opened 2026-01-22 15:28:44 +01:00 by admin · 0 comments
Owner

Originally created by @flaushi on GitHub (Mar 20, 2019).

Originally assigned to: @lcobucci on GitHub.

Bug Report

My DQL query selects a number from an entity:

$query = $this->_em->createQuery("
            SELECT s.value AS value
            FROM  App\Entity\DataSample s 
            WHERE ...

");
$query->setMaxResults(1);

This query is executed tens of thousands of times inside a Symfony console application.
Now if I use getSingleScalarResult the memory consumption will explode.

try {
            $r = $query->getSingleScalarResult();
        } catch (NoResultException $e) {
            unset($e);
            $r = '0.0';
        }
        unset($query);
        return $r;

However, switching to getResult helps:

$r = $query->getResult();

        if(empty($r)){
           $r = '0.0';
        } else
            $r = $r[0]['value'];
        return $r;

In the latter case, memory consumption stays bearable.

I found this issue and developed a TestCase.

<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Annotation as ORM;
use Doctrine\ORM\NoResultException;
use Doctrine\Tests\OrmFunctionalTestCase;

/**
 * @group GH7642
 */
class GH7642Test extends OrmFunctionalTestCase
{

    private $limit = 25000;
    private $numIterations = 10000;
    /**
     * {@inheritDoc}
     */
    protected function setUp() : void
    {
        parent::setUp();

        $this->schemaTool->createSchema(
            [
                $this->em->getClassMetadata(GH7642Entity::class),
            ]
        );

    }

    private function queryResult() {
        $query = $this->em->createQuery(<<<DQL
            SELECT e.points AS value FROM Doctrine\Tests\ORM\Functional\Ticket\GH7642Entity e
DQL
        );
        $query->setMaxResults(1);
        $queryResult = $query->getResult();
        if(empty($queryResult))
            return 0;
        else
            return $queryResult[0]['value'];
    }

    private function standardGetResultCase() : float {

        // iterate over results
        for ($i = 0 ; $i < $this->numIterations ; $i++){
            $this->queryResult();
        }
        return memory_get_peak_usage(true) / 1000000; // in MB

    }

    private function queryWithSingleScalarResult()  {
        $query = $this->em->createQuery(<<<DQL
            SELECT e.points AS value FROM Doctrine\Tests\ORM\Functional\Ticket\GH7642Entity e
DQL
    );
        $query->setMaxResults(1);
        try {
            $r = $query->getSingleScalarResult();
        } catch (NoResultException $e){
            $r = 0;
        }
        return $r;
    }

    private function singleScalarCase() : float {

        // iterate over results
        for ($i = 0 ; $i < $this->numIterations ; $i++){
            $this->queryWithSingleScalarResult();
        }
        return memory_get_peak_usage(true) / 1000000; // in MB

    }

    public function testIssue() : void
    {

        for($i=0;$i<$this->limit;$i++) {
            $entity = new GH7642Entity(mt_rand(0,100));

            $this->em->persist($entity);
        }

        $this->em->flush();
        $this->em->clear();
        gc_collect_cycles();

        $rawQueryMemory = $this->standardGetResultCase();

        // clear all
        $this->em->flush();
        $this->em->clear();
        gc_collect_cycles();

        $namedQueryMemory = $this->singleScalarCase();

        var_dump([
            'standard'  =>$rawQueryMemory,
            'singleScalar' => $namedQueryMemory]);
        // lets assume that named query should use 2x more memory at most.
        $this->assertLessThanOrEqual($rawQueryMemory*2, $namedQueryMemory);

    }

}

/**
 * @ORM\Entity
 */
class GH7642Entity
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    public $id;

    /**
     * @var int
     * @ORM\Column(type="decimal", precision=20, scale=4)
     */
    public $points;

    public function __construct($points)
    {
        $this->points = $points;
    }
}

Unfortunately the test case does not produce the memory leak.

However, when repeating the queries, memory consumption does increase. This should not be the case IMHO?

for ($i = 0 ; $i < 100 ; $i++) {
//            $rawQueryMemory = $this->standardGetResultCase();
// or:
            $rawQueryMemory = $this->singleScalarCase();
            var_dump($rawQueryMemory);
        }

gives

float(88.08448)
float(88.08448)
float(88.08448)
float(88.08448)
float(90.181632)
float(90.181632)
float(90.181632)
float(90.181632)
float(90.181632)
float(90.181632)
float(94.375936)
float(94.375936)
float(98.57024)
float(100.667392)
float(104.861696)
float(109.056)
float(113.250304)
float(117.444608)
float(119.54176)
float(123.736064)
float(127.930368)
float(132.124672)

Fatal error: Allowed memory size of 134217728 bytes exhausted 

One has to say that I had to switch to PHP 7.2 for the test case to compile and also pulled orm from master branch. My software uses PHP 7.1 and ORM 2.6.2

doctrine/annotations                 v1.6.0   MIT                
doctrine/cache                       v1.8.0   MIT                
doctrine/collections                 v1.5.0   MIT                
doctrine/common                      v2.10.0  MIT                
doctrine/data-fixtures               v1.3.1   MIT                
doctrine/dbal                        v2.9.2   MIT                
doctrine/doctrine-bundle             1.10.1   MIT                
doctrine/doctrine-cache-bundle       1.3.5    MIT                
doctrine/doctrine-fixtures-bundle    3.1.0    MIT                
doctrine/doctrine-migrations-bundle  v1.3.2   MIT                
doctrine/event-manager               v1.0.0   MIT                
doctrine/inflector                   v1.3.0   MIT                
doctrine/instantiator                1.1.0    MIT                
doctrine/lexer                       v1.0.1   MIT                
doctrine/migrations                  v1.8.1   MIT                
doctrine/orm                         v2.6.2   MIT                
doctrine/persistence                 v1.1.0   MIT                
doctrine/reflection                  v1.0.0   MIT   
Originally created by @flaushi on GitHub (Mar 20, 2019). Originally assigned to: @lcobucci on GitHub. ### Bug Report My DQL query selects a number from an entity: ``` $query = $this->_em->createQuery(" SELECT s.value AS value FROM App\Entity\DataSample s WHERE ... "); $query->setMaxResults(1); ``` This query is executed tens of thousands of times inside a Symfony console application. Now if I use `getSingleScalarResult` the memory consumption will explode. ``` try { $r = $query->getSingleScalarResult(); } catch (NoResultException $e) { unset($e); $r = '0.0'; } unset($query); return $r; ``` However, switching to `getResult` helps: ``` $r = $query->getResult(); if(empty($r)){ $r = '0.0'; } else $r = $r[0]['value']; return $r; ``` In the latter case, memory consumption stays bearable. I found [this issue](https://github.com/doctrine/orm/issues/7642) and developed a TestCase. ``` <?php declare(strict_types=1); namespace Doctrine\Tests\ORM\Functional\Ticket; use Doctrine\ORM\Annotation as ORM; use Doctrine\ORM\NoResultException; use Doctrine\Tests\OrmFunctionalTestCase; /** * @group GH7642 */ class GH7642Test extends OrmFunctionalTestCase { private $limit = 25000; private $numIterations = 10000; /** * {@inheritDoc} */ protected function setUp() : void { parent::setUp(); $this->schemaTool->createSchema( [ $this->em->getClassMetadata(GH7642Entity::class), ] ); } private function queryResult() { $query = $this->em->createQuery(<<<DQL SELECT e.points AS value FROM Doctrine\Tests\ORM\Functional\Ticket\GH7642Entity e DQL ); $query->setMaxResults(1); $queryResult = $query->getResult(); if(empty($queryResult)) return 0; else return $queryResult[0]['value']; } private function standardGetResultCase() : float { // iterate over results for ($i = 0 ; $i < $this->numIterations ; $i++){ $this->queryResult(); } return memory_get_peak_usage(true) / 1000000; // in MB } private function queryWithSingleScalarResult() { $query = $this->em->createQuery(<<<DQL SELECT e.points AS value FROM Doctrine\Tests\ORM\Functional\Ticket\GH7642Entity e DQL ); $query->setMaxResults(1); try { $r = $query->getSingleScalarResult(); } catch (NoResultException $e){ $r = 0; } return $r; } private function singleScalarCase() : float { // iterate over results for ($i = 0 ; $i < $this->numIterations ; $i++){ $this->queryWithSingleScalarResult(); } return memory_get_peak_usage(true) / 1000000; // in MB } public function testIssue() : void { for($i=0;$i<$this->limit;$i++) { $entity = new GH7642Entity(mt_rand(0,100)); $this->em->persist($entity); } $this->em->flush(); $this->em->clear(); gc_collect_cycles(); $rawQueryMemory = $this->standardGetResultCase(); // clear all $this->em->flush(); $this->em->clear(); gc_collect_cycles(); $namedQueryMemory = $this->singleScalarCase(); var_dump([ 'standard' =>$rawQueryMemory, 'singleScalar' => $namedQueryMemory]); // lets assume that named query should use 2x more memory at most. $this->assertLessThanOrEqual($rawQueryMemory*2, $namedQueryMemory); } } /** * @ORM\Entity */ class GH7642Entity { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue */ public $id; /** * @var int * @ORM\Column(type="decimal", precision=20, scale=4) */ public $points; public function __construct($points) { $this->points = $points; } } ``` Unfortunately the test case does not produce the memory leak. However, when repeating the queries, memory consumption does increase. This should not be the case IMHO? ``` for ($i = 0 ; $i < 100 ; $i++) { // $rawQueryMemory = $this->standardGetResultCase(); // or: $rawQueryMemory = $this->singleScalarCase(); var_dump($rawQueryMemory); } ``` gives ``` float(88.08448) float(88.08448) float(88.08448) float(88.08448) float(90.181632) float(90.181632) float(90.181632) float(90.181632) float(90.181632) float(90.181632) float(94.375936) float(94.375936) float(98.57024) float(100.667392) float(104.861696) float(109.056) float(113.250304) float(117.444608) float(119.54176) float(123.736064) float(127.930368) float(132.124672) Fatal error: Allowed memory size of 134217728 bytes exhausted ``` One has to say that I had to switch to PHP 7.2 for the test case to compile and also pulled orm from master branch. My software uses PHP 7.1 and ORM 2.6.2 ``` doctrine/annotations v1.6.0 MIT doctrine/cache v1.8.0 MIT doctrine/collections v1.5.0 MIT doctrine/common v2.10.0 MIT doctrine/data-fixtures v1.3.1 MIT doctrine/dbal v2.9.2 MIT doctrine/doctrine-bundle 1.10.1 MIT doctrine/doctrine-cache-bundle 1.3.5 MIT doctrine/doctrine-fixtures-bundle 3.1.0 MIT doctrine/doctrine-migrations-bundle v1.3.2 MIT doctrine/event-manager v1.0.0 MIT doctrine/inflector v1.3.0 MIT doctrine/instantiator 1.1.0 MIT doctrine/lexer v1.0.1 MIT doctrine/migrations v1.8.1 MIT doctrine/orm v2.6.2 MIT doctrine/persistence v1.1.0 MIT doctrine/reflection v1.0.0 MIT ```
admin added the BugInvalid labels 2026-01-22 15:28:44 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#6200