Tăng hiệu năng khi dùng Doctrine trong Symfony2

Doctrine ORM là một công cụ mạnh mẽ mà có thể tạo điều kiện cho một truy cập vào lớp cơ sở dữ liệu, nó cung cấp kết nối (mapping) các dữ liệu nguồn với các đối tượng (objects). Tuy nhiên, như tất cả các lớp trừu tượng, để có khả năng như vậy thì nó cũng đi kèm với hiệu năng phải cao.

Dựa trên một số trường hợp sử dụng thông thường, bài viết giúp cho việc nâng cao hiệu suất khi sử dụng Doctrine ORM.

1. Luôn luôn kiểm soát Symfony2 Profiler Toolbar

Việc đầu tiên, nguyên tắc quan trọng của việc phát triển dự án Symfony2 đạt hiệu quả là phải luôn luôn kiểm soát những gì đang xảy ra trong Profiler Toolbar.

Nhờ nó, bạn luôn có thể nhận ra điểm yếu trong ứng dụng của bạn. ** Sự gia tăng của số lượng các truy vấn có thể cho bạn thấy rằng một cái gì đó có thể không có hiệu quả, một số truy vấn (query) có thể bị thừa.**

Trong Symfony Profiler bạn sẽ nhận được chi tiết của các truy vấn cơ sở dữ liệu:

  • Số lượng truy vấn được thực thi
  • Thời gian thực thi truy vấn
  • Chi tiết các truy vấn

Bạn click vào để nhận được thêm các thông tin chi tiết hơn nữa Screen Shot 2015-06-26 at 9.15.14 AM.png

2. Tránh việc lạm phát các object khi sử xử lý nhiều entity

Quá trình nạp các đối tượng là một trong những quá trình tốn thời gian nhất và tốn bộ nhớ nhất trong ORM.

Khi bạn lấy nhiều hàng từ cơ sở dữ liệu chỉ để hiển thị chúng trong các view (ví dụ: như trong một số loại bảng liệt kê/CRUD), việc nạp các đối tượng có thể không có ý nghĩa.

Thay vào đó bạn nên sử dụng chế độ nạp các đối tượng vào các object đơn giản hơn như arrays/scalar:

<?php

$qb = new \Doctrine\ORM\QueryBuilder;

$arrayResults  = $qb->getQuery()->getArrayResult();
$scalarResults = $qb->getQuery()->getScalarResult();

3. Không tải toàn bộ entity nếu bạn chỉ cần tham chiếu đến nó

Đôi khi bạn ở trong tình hình khi có một Entity và một ID của một entity khác mà bạn sẽ muốn liên kết. Trong trường hợp đó bạn sử dụng find($id) - SELECT - điều này có thể dẫn đến việc thêm một lệnh không cần thiết vào cơ sở dữ liệu.

Nhờ có Doctrine’s Reference Proxies, bạn sẽ không phải lấy toàn bộ entity từ cơ sở dữ liệu mà chỉ phải liên kết nó với một thực thể khác.

<?php

$em = ...; // đối tượng \Doctrine\ORM\EntityManager
$friendId = ...; // ID của một user entity khác

$user = new User;
$user->addFriend($em->getReference('AppBundle\Entity\User', $friendId));

$em->persist($user);
$em->flush();

Doctrine Docs – Reference Proxies

4. Sử dụng lệnh Update khi muốn cập nhật nhiều row trong CSDL

Khi bạn phải cập nhật nhiều entity, việc lấy tất cả từ cơ sở dữ liệu ra và lặp qua lặp lại các entity ORM là một cách làm tồi tệ.

Bạn không bao giờ nên làm như thế này:

<?php

$friend = $em->getReference('AppBundle\Entity\User', $friendId);
$users = $this->findAll();

foreach ($users as $user) {
    $user->setFriend($friend);
    $em->persist($user);
}

$em->flush();

Thay vào đó, bạn nên dựa trên truy vấn UPDATE:

<?php
$qb->update('AppBundle:User', 'u')
    ->set('u.friend', $friendId)
    ->getQuery()->execute();

Nhờ đó, chúng ta chỉ thực hiện một câu lệnh UPDATE SQL duy nhất thay vì N lần cập nhật cho từng đối tượng người dùng.

5. Sử dụng lợi thế của Lazy Collections

Từ phiên bản 2.1, Doctrine đã được thêm việc hỗ trợ lazy collections.

http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html#extra-lazy-associations

ManyToMany hoặc OneToMany được định nghĩa với kiểu EXTRA_LAZY:

/**
 * @Entity
 */
class TestGroup
{
    /**
     * @ManyToMany(targetEntity="TestUser", mappedBy="groups", fetch="EXTRA_LAZY")
     */
    public $users;
}

nó tạo ra cái gọi là phương thức của Collection giống như:

  • $users->count();
  • $users->slice(…)
  • … và nó sẽ không tải toàn bộ collection từ csdl vào bộ nhớ/

Thay vào đó, Doctrine sẽ khéo léo thực hiện truy vấn thích hợp như COUNT.

6. Hãy coi chừng khi nạp các entity trong vòng lặp

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. Theo mặc định, Doctrine sẽ không tạo ra JOIN cho các thực thể liên quan. Tình hình chung là khi bạn có các entity với N-1 (ví dụ như một bài viết có thể chỉ có một tác giả) và bạn muốn hiển thị một danh sách các bài viết với tên tác giả.

Hãy xem đoạn code mẫu sau:

Trong một vài controller bạn sẽ phải tìm tất cả/một vài bài viết:

<?php

$em = $this->getEntityManager();

$articlesRepo = $em->getRepository('AppBundleBundle:Article');
$articles = $articlesRepo->findAll();

Trong view bạn muốn hiển thị tên cuấc giả bài viết:

{% for article in articles %}
    {{ article.author.name }}
    {{ article.content }}
{% endfor %}

Đoạn code trên sẽ tạo thêm một lệnh SELECT cho mỗi tác giả của bài viết

Trong trường hợp này, bạn nên sử dụng JOIN trong DQL/Query Builder:

<?php

$qb = $em->createQueryBuilder('qb1');
$qb->add('select', 'a, au')
        ->add('from', 'AppBundleBundle:Article a')
        ->join('a.author', 'au');

khi đó bạn sẽ nhận được bài viết kèm theo tác giả chỉ trong 1 câu truy vấn. Vì vậy khi bạn gọi đến article.getAuthor() nó sẽ không thực hiện thêm bất kỳ truy vấn cơ sở dữ liệu nào khác.