2014-04-18 12 views
5

Tôi đang sử dụng Trình điều hợp Pagerfanta và Doctrine với Symfony2 và Silex. Khi cơ sở dữ liệu của tôi trở nên lớn hơn, tôi nhận thấy tải rất lớn trên các trang thống kê quản trị hiển thị dữ liệu lớn với phân trang. Tôi đã kiểm tra hồ sơ và thấy các truy vấn không hiệu quả không thể tin được:Tối ưu hóa Pagerfanta và Doctrine2 COUNT

SELECT DISTINCT id16 
FROM (
    SELECT f0_.username AS username0, ..., f0_.added_on AS added_on20 
    FROM fos_user f0_ ORDER BY f0_.id DESC 
) dctrn_result 
LIMIT 50 OFFSET 0; 

SELECT COUNT(*) AS dctrn_count 
FROM (
    SELECT f0_.username AS username0, ..., f0_.added_on AS added_on20 
    FROM fos_user f0_ ORDER BY f0_.id DESC 
) dctrn_result 
LIMIT 50 OFFSET 0;` 

Truy vấn đầu tiên dễ sửa bằng cách tạo phiên bản cố định của lớp DoctrineORMAdapter. Mã tạo truy vấn COUNT() phức tạp hơn nên tôi quyết định hỏi xem có giải pháp nào cho việc này hay không.

Vậy có cách nào để Pagerfanta không chạy các truy vấn lồng nhau không?

+0

Nếu bạn thấy đoạn mã tạo ra một truy vấn phức tạp không cần thiết, có lẽ bạn nên viết một báo cáo lỗi tại trang web của dự án. – lxg

Trả lời

5

muộn còn hơn không: tôi đã đâm vào tường cùng một ngày hôm nay với> 200k hồ sơ và tìm ra giải pháp.

Pagerfanta nội bộ sử dụng Học thuyết \ ORM \ Tools \ Pagination \ CountOutputWalker để đếm đối tượng mà kết quả trong một truy vấn số như thế này:

SELECT 
    COUNT(*) AS dctrn_count 
FROM 
    (
    SELECT 
     DISTINCT id_0 
    FROM 
     (
     SELECT 
      m0_.id AS id_0, 
      ... 
     FROM 
      messaging_messages m0_ 
     ORDER BY 
      m0_.id DESC 
    ) dctrn_result 
) dctrn_table 

Để bỏ qua CountOutputWalker chúng ta có thể vượt qua một lá cờ khi instantiating DoctrineORMAdapter. Vì vậy, thay vì chỉ đơn giản là

$adapter = new DoctrineORMAdapter($qb); 

bạn làm

$adapter = new DoctrineORMAdapter($qb, true, false); 

(tham số thứ ba). Điều này biến truy vấn đếm thành truy vấn có hiệu quả hơn nhiều:

SELECT 
    count(DISTINCT m0_.id) AS sclr_0 
FROM 
    messaging_messages m0_ 

Bạn phải cập nhật whiteoctober/Pagerfanta lên 1.0.3.

Issue

Related commit

0

Trong trường hợp của bạn, không phải pagerfanta thực hiện truy vấn phụ. Đó là nguồn trình tạo trình tạo truy vấn của bạn mà nó đến từ đó.

Tôi thường có chức năng trong kho lưu trữ thực thể trả về phiên bản trình tạo truy vấn đơn giản thay vì kết quả. Bạn còn viết một trình xây dựng truy vấn hiệu quả. Sau đó, tôi cung cấp trình tạo truy vấn đó vào DoctrineORMAdapter.

Tôi đã có chức năng này helper mà tôi sử dụng trong suốt dự án của tôi:

/** 
* Pass an array, entity or a custom QueryBuilder instance to paginate. 
* Takes an array of parameters as a second argument. 
* Default parameter values: 
* 
* $params = array(
*  'curPage' => 1, 
*  'perPage' => 15, 
*  'order' => 'DESC' 
*); 
* 
* @param mixed $object 
* @param array $params 
* 
* @return Pagerfanta 
*/ 
public function paginate($object, $params = array()) 
{ 
    if (is_array($object)) { 
     $adapter = new ArrayAdapter($object); 
    } elseif ($this->isEntity($object)) { 
     $qb  = $this->em->createQueryBuilder() 
      ->select('s') 
      ->from($this->getEntityName($object), 's') 
      ->orderBy('s.id', isset($params['order']) ? $params['order'] : 'DESC'); 
     $adapter = new DoctrineORMAdapter($qb); 
    } elseif ($object instanceof QueryBuilder) { 
     $adapter = new DoctrineORMAdapter($object); 
    } 
    $pager = new Pagerfanta($adapter); 
    $pager->setMaxPerPage(isset($params['perPage']) ? $params['perPage'] : 15); 
    $pager->setCurrentPage(isset($params['curPage']) ? $params['curPage'] : 1); 

    return $pager; 
} 

Bạn có thể vượt qua một mảng, tổ chức hoặc một trường hợp xây dựng truy vấn và nó sẽ trả về một đối tượng thích hợp paginated sẵn sàng để sử dụng.

Bạn có biết làm thế nào nó được thực hiện, nhưng dù sao, đây là những gì tôi có trong kho thực thể của tôi - một hàm trả về Ví dụ xây dựng truy vấn (hoàn hảo cho pagerfanta), lợi nhuận khác một mảng được sử dụng ở những nơi khác:

public function getMessageQueryBuilder($campaignId, $eqCriteriaArray = array(), $neqCriteriaArray = array()) 
{ 
    $qb = $this->createQueryBuilder('m'); 
    $qb->select('m') 
     ->leftJoin('m.campaign', 'c') 
     ->leftJoin('m.sentBy', 'u') 
     ->where($qb->expr()->eq('m.campaign', $campaignId)); 
    foreach ($eqCriteriaArray as $property => $value) { 
     $qb->andWhere($qb->expr()->eq($property, $qb->expr()->literal($value))); 
    } 
    foreach ($neqCriteriaArray as $property => $value) { 
     $qb->andWhere($qb->expr()->neq($property, $qb->expr()->literal($value))); 
    } 

    return $qb->orderBy('m.id', 'DESC'); 
} 

public function filterMessages($campaignId, $eqCriteriaArray = array(), $neqCriteriaArray = array()) 
{ 
    return $this->getMessageQueryBuilder($campaignId, $eqCriteriaArray, $neqCriteriaArray)->getQuery()->getResult(); 

Sau đó, tôi kết hợp hai để có được những đối tượng pager thực tế:

$singleSmsPager = $this->pagerUtil->paginate(
    $this->em->getRepository('TreasureForgeMessageBundle:Message') 
     ->getMessageQueryBuilder(CcToolSender::CAMPAIGN_ID, array(), array('u.username' => 'admin')), 
    array(
     'curPage' => $singleSmsPage, 
     'perPage' => 10 
    ) 
); 
+0

Tôi không nghĩ vậy.QueryBuilder của tôi (cái tôi chuyển tới bộ điều hợp) chỉ chịu trách nhiệm cho truy vấn chọn ban đầu. Cách Pagerfanta tạo truy vấn 'COUNT' từ nó là điều khác. – yefrem

+0

Xin lỗi, tôi đã kiểm tra các truy vấn phân trang của riêng mình và chúng dường như làm cùng một kiểu truy vấn phụ. Nhưng sau đó tôi đã không nhận thấy bất kỳ mối quan hệ giữa hiệu suất và số lượng hồ sơ trên ứng dụng của tôi. Tôi đã nghĩ mysql optimiser sẽ giới hạn kết quả bên trong thiết lập quá trong trường hợp như vậy. Hấp dẫn. –

+0

Tôi đã nhận thấy sự cố khi bảng của tôi đạt đến hàng trăm nghìn bản ghi. Nó cũng có tỷ lệ cập nhật cao vì vậy tôi nghĩ rằng kết quả không thể được lưu trữ. Trên trang web khác, tôi có khoảng 2000 mục được phân trang và cập nhật mỗi ngày một lần và không gặp vấn đề gì – yefrem