Doctrine ORM is a powerful tool which can facilitate an access to the database layer providing mapping the source data to objects. Unfortunately, as every next abstraction layer, such possibilities come with a performance overhead.
Based on some common use-cases, the article will cover topics related with improving the performance when using Doctrine ORM. We’ve choosen it as it comes in a Symfony Standard distribution and is integrated out-of-box within your project — it’s the most commonly ORM used in .
0. Always control your Symfony2 Profiler toolbar
The first, key rule of developing efficient Symfony2 project is to always control what is going on in the profiler’s toolbar.
Thanks to it, you can always spot weak points of your application – rise of the number of queries can show you that something may be inefficient, some queries may leak.
In the Symfony Profiler you can also get into details of the database queries:
Click on the blue (+) to get a nicely formatted query
Time – shows the time of the statement execution
Explain query – shows an of the query
Btw. click on the profiler below to get the queries explained
1. Avoid Object Hydration when handling many entities
The process of Hydration is one of the most time and memory consuming in the ORM.
When you’re retrieving many rows from the database only to show them in the view (e.g. in a some sort of listing/CRUD), hydrating them to the object may not make sense.
Instead you should use simpler hydration modes like arrays/scalar:
getQuery()->getArrayResult();$scalarResults = $qb->getQuery()->getScalarResult();
2. Don’t load the whole entity if you only need reference to it
Sometimes you’re in the situation when having an Entity and an ID of the other one which you will like to associate. In that case making an extra find($id) operation — SELECT — can lead to an unnecessary additional database statement.
Thanks to the Doctrine’s Reference Proxies you don’t have to retrieve the whole entity from the database only to associate it with another one.
You can use its ID instead:
addFriend($em->getReference('Octivi\Entity\User', $friendId));$em->persist($user);$em->flush();
3. Update multiple database rows using Update statement
When you have to update multiple entities, retrieving them all from the database and iterating over ORM entities is known as a bad practice.
You should never do like:
getReference('Octivi\Entity\User', $friendId);$users = $this->findAll();foreach ($users as $user) { $user->setFriend($friend); $em->persist($user);}$em->flush();
Instead, you should rely on the UPDATE query:
update('Octivi:User', 'u') ->set('u.friend', $friendId) ->getQuery()->execute();
Thanks to it, we only execute one SQL UPDATE statement instead of N-updates for each User entity.
4. Use the advantages of Lazy Collections
From the 2.1 version, Doctrine added support for lazy collections.
With extra lazy collections you can now not only add entities to large collections but also paginate them easily using a combination of count and slice.
Having ManyToMany or OneToMany mappings defined with fetch type of EXTRA_LAZY:
/** * @Entity */class CmsGroup{ /** * @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY") */ public $users;}
makes, that calling Collection’s methods like:
$users->count();
$users->slice(…)
…
won’t load the whole collection from the database to the memory.
Instead, Doctrine will cleverly execute appropriate queries such as COUNT.
5. Beware of loading entities in a loop
By default, Doctrine won’t make JOIN for associated entities. The common situation is when you have entities with N-1 association (e.g. one article can have only one author) and you want to display a list of articles with their author’s names.
Take a look at the sample code:
In some controller you are finding all/some articles:
getEntityManager();$articlesRepo = $em->getRepository('OctiviTestBundle:Article');$articles = $articlesRepo->findAll();
In the view you want to display the name of the article’s author:
{% for article in articles %} { { article.author.name }} { { article.content }}{% endfor %}
Such code will lead to additional SELECT statements for every article’s author.
Instead, you should use JOIN in your DQL/Query Builder:
createQueryBuilder('qb1');$qb->add('select', 'a, au') ->add('from', 'OctiviTestBundle:Article a') ->join('a.author', 'au');
which will get articles with their authors in one SELECT query. So next calls for article.getAuthor()
won’t make any extra database queries.