2014-04-18 6 views
5

Używam Pagerfanta i Doctrine Adapters z Symfony2 i Silex. W miarę jak moja baza danych stawała się coraz większa, zauważyłem duże obciążenie na stronach statystyk administracyjnych, które wyświetlają duże dane przy pomocy paginacji. Sprawdziłem profilera i zobaczył niewiarygodnie nieefektywnych zapytania:Pagerfanta i Doctrine2 Optymalizacja 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;` 

pierwsze zapytanie było łatwo rozwiązać poprzez stworzenie ustaloną wersję DoctrineORMAdapter klasie. Kod generujący zapytanie COUNT() jest bardziej skomplikowany, więc zdecydowałem się zapytać, czy jest na to jakieś rozwiązanie.

Czy istnieje sposób, aby Pagerfanta nie uruchamiał zapytań zagnieżdżonych?

+0

Jeśli pojawi się kod generujący niepotrzebnie skomplikowane zapytania, może należy napisać raport o błędzie na stronie projektu. – lxg

Odpowiedz

5

Lepiej późno niż nigdy: mam hit tej samej ścianie dziś> 200K rekordów i znalazł rozwiązanie.

Pagerfanta używa wewnętrznie Doctrine \ ORM \ Tools \ Pagination \ CountOutputWalker liczyć przedmioty, które skutkuje zapytania count tak:

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 

Aby ominąć CountOutputWalker możemy przekazać flagę przy uruchamianiu DoctrineORMAdapter. Więc zamiast po prostu

$adapter = new DoctrineORMAdapter($qb); 

zrobić

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

(trzeci parametr). Włącza zapytanie liczyć na znacznie bardziej efektywne jeden:

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

Musisz zaktualizować whiteoctober/Pagerfanta do 1.0.3 chociaż.

Issue

Related commit

0

W twoim przypadku to nie pagerfanta wykonuje pod-zapytania. Jest to źródło, z którego pochodzi instancja konstruktora zapytań.

Zwykle mam funkcję w repozytorium jednostek, która zwraca instancję zwykłego konstruktora kwerendy zamiast wyników. Pozostaje Ci napisanie wydajnego narzędzia do tworzenia zapytań. Następnie przekazuję ten kreator zapytań do DoctrineORMAdapter.

Mam tę funkcję pomocnika, który używam w całym moich projektów:

/** 
* 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; 
} 

Można przekazać tablicę, podmiot lub instancję kreator zapytań i powróci odpowiednio paginowane obiekt gotowy do użycia.

Zapewne wiesz jak to zrobić, ale w każdym razie, oto co mam w moim repozytorium jednostki - jedna funkcja zwraca instancję kwerend (Idealny dla pagerfanta), a drugi zwraca tablicę być wykorzystywane gdzie indziej:

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(); 

Potem połączyć te dwa, aby uzyskać rzeczywisty obiekt pagera:

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

Nie sądzę.Mój queryBuilder (ten, który przekazuję adapterowi) odpowiada tylko za oryginalne zapytanie. Sposób, w jaki Pagerfanta tworzy z niego zapytanie "COUNT", jest czymś zupełnie innym. – yefrem

+0

Przepraszam, sprawdziłem własne zapytania dotyczące stronicowania i wydaje się, że wykonują one ten sam rodzaj sub-zapytań. Ale nie zauważyłem żadnego związku między wydajnością a liczbą rekordów w mojej aplikacji. Myślę, że w takich przypadkach optymalizacja mysql ograniczyłaby również wewnętrzny zestaw wyników. Ciekawy. –

+0

Zauważyłem problemy, gdy mój stół osiągnął setki tysięcy rekordów. Miał również wysoką częstotliwość aktualizacji, więc myślę, że wynik nie może być buforowany. Na innej stronie mam około 2000 przedmiotów podzielonych na strony i aktualizowanych raz dziennie i nie widzę problemów – yefrem