Я уже давно намеревался написать эту статью, но все никак не доходили руки. Ну вот, я собрался с мыслями и сделал это. Значит, о чем пойдет речь... Я поделюсь некоторыми приемами работы с Doctrine2 ORM, совершим, так сказать, повышение производительности сайта на Symfony 2 (точнее, любого сайта, который использует Doctrine2 ORM). Как наглядное пособие, я создал проект и выложил его на GitHub, так что теперь любой желающий может проверить мои слова в действии.
1. Загрузка всех необходимых связей
Я считаю этот прием наиболее эффективным и поэтому поставил его на первое место.
Скажем, у нас есть 5 авторов и их 100 постов, и нам необходимо вывести все посты с именами авторов на страницу.
Шаблон:
{% for post in posts %} <article><h2>{{ post.title }}</h2><p>Author: {{ post.author.firstName }} {{ post.author.lastName }} created at {{ post.createdAt | date('d-m-Y H:i') }}</p><p>{{ post.text }}</p></article> {% endfor %}
Что может быть проще, когда нужна оптимизация работы Symfony 2, скажете вы и сделаете что-то похожее на это:
$posts=$this->getDoctrine()->getRepository('AcmeDemoBundle:Post')->findAll();
Но если взглянуть в профайлер (Symfony Profiler Toolbar), то можно увидеть такую печальную информацию:
Откуда же столько запросов к базе данных, если мы сделали только один запрос?
Все дело в том, что Doctrine2 ORM не загружает по умолчанию сущности из связей, и, когда мы обращаемся к ним, происходит новый запрос на получение этих данных. Вот поэтому у нас еще 5 запросов на получение авторов постов, которые никак не способствуют повышению производительности сайта на Symfony 2.
Для того, чтобы оптимизировать запрос, нужно воспользоваться JOINом и добавить нужные сущности в выборку. Вот оптимизированный код метода репозитория на получение списка постов.
/** * Find all posts with authors * * @return array | Post[] */publicfunction findAllPostsAndAuthors(){$qb=$this->createQueryBuilder('p');$qb->addSelect('a')->innerJoin('p.author','a'); return$qb->getQuery()->getResult();}
Воспользовавшись этим методом для получения списка постов, мы получаем следующую картину в профайлере:
Теперь у нас остался только 1 запрос, время выполнения которого уменьшилось по сравнению с предыдущим вариантом и произошло повышение производительности Doctrine 2 orm.
2. Обновление нескольких сущностей запросом
Допустим, нам теперь нужно обновить дату создания всех наших постов. Часто это делают получением всех постов из базы и потом в цикле обновляют каждую сущность в отдельности:
$newCreatedAt=new \DateTime();$posts=$this->getDoctrine()->getRepository('AcmeDemoBundle:Post')->findAll(); /** @var Post $post */foreach($postsas$post){$post->setCreatedAt($newCreatedAt);}$this->getDoctrine()->getManager()->flush();
В результате чего имеем следующие значения в профайлере после обновления 100 постов:
Как видно, было выполнено аж 103 запроса к базе данных. Даже профайлер пометил их желтым, что уже должно вас насторожить и заставить подумать об оптимизации.
Что же привело к такому количеству запросов? Из-за того, что мы обновляли сущности в цикле, мы получили большое количество UPDATE запросов в базу данных.
Оптимизация работы Doctrine2 ORM. Нам нужно всего-навсего написать один запрос для обновления сразу всех записей в базе данных.
/** * Update created date for all posts * * @param \DateTime $newCreatedAt * * @return int */publicfunction updateCreatedAtForAllPosts(\DateTime $newCreatedAt){$qb=$this->createQueryBuilder('p');$qb->update()->set('p.createdAt',':newCreatedAt')->setParameter('newCreatedAt',$newCreatedAt); return$qb->getQuery()->execute();}
После чего мы получим только один запрос к базе данных и также сократим время выполнения.
3. Отказ от гидрации
Что такое гидрация? Гидрация — преобразование массива в объект и обратно.
Гидрация является самым затратным процессом по времени и по памяти в ORM.
Поэтому при получении большого количества данных из базы данных только для их отображения, например, при выводе списка, гидрировать все в объекты сущностей будет очень затратно. Лучше получить данные в виде ассоциативного массива и сэкономить ресурсы.
$posts=$this->createQueryBuilder('p')->getQuery()->getArrayResult();
Подробнее узнать о способах гидрации можно из документации Doctrine2 ORM.
4. Использование Reference Proxies
Допустим, нам нужно добавить в пост связь с автором, и у нас есть ID этого автора. В большинстве случаев это выполняют получением сущности автора из базы данных по его ID и затем просто устанавливают связь автора с постом:
$author=$this->getDoctrine()->getRepository('AcmeDemoBundle:Author')->find($autorId); $post=new Post();$post->setAuthor($author);
Как оказалось, получение автора из базы данных — это совершенно лишний запрос, от которого очень легко избавиться. Для это у Doctrine2 есть Reference Proxies. Вот как это работает:
$em=$this->getDoctrine()->getManager(); $post=new Post();$post->setAuthor($em->getReference('Acme\DemoBundle\Entity\Author',$authorId));
Таким образом мы избавились от лишнего запроса и выполнили связь, имея только ID автора.
5. Использование Symfony Profiler Toolbar
Несмотря на то, что это последний пункт, этот совет является не менее важным. Постоянный контроль того, что происходит в профайлере, значительно поможет вам разрабатывать эффективные проекты на Symfony2. Благодаря профайлеру вы всегда будете держать «руку на пульсе» ваших запросов к базе данных и сможете вовремя выполнять оптимизацию работы Symfony 2 для увеличения производительности вашего проекта.
Подробнее ознакомиться с архитектурой Doctrine 2 вы можете здесь.
Stfalcon.com приглашает вас к сотрудничеству!