2014-04-18 20 views
5

Uso gli adattatori Pagerfanta e Doctrine con Symfony2 e Silex. Quando il mio database si è ingrandito, ho notato un carico enorme sulle pagine delle statistiche di amministrazione che mostrano grandi quantità di dati con l'impaginazione. Ho controllato e ho visto profiler query incredibilmente inefficienti:Ottimizzazione Pagerfanta e 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;` 

prima interrogazione era facile da risolvere con la creazione di versione fissa di DoctrineORMAdapter di classe. Il codice che genera la query COUNT() è più complicato, quindi ho deciso di chiedere se esiste una soluzione per questo.

Quindi c'è un modo per rendere Pagerfanta non in esecuzione query annidate?

+0

Se si vede il codice che genera una query complicata inutile, forse si dovrebbe scrivere un bug report presso il sito del progetto. – lxg

risposta

5

Meglio tardi che mai: ho colpito lo stesso muro oggi con> 200k record e trovato una soluzione.

Pagerfanta utilizza internamente Doctrine \ ORM \ Tools \ paginazione \ CountOutputWalker per contare gli oggetti che si traduce in una query conteggio simili:

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 

Per bypassare CountOutputWalker possiamo passare un flag quando un'istanza DoctrineORMAdapter. Così, invece di semplicemente

$adapter = new DoctrineORMAdapter($qb); 

fate

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

(terzo parametro). Questo trasforma la query conteggio in una molto più efficiente uno:

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

Dovete aggiornare whiteoctober/Pagerfanta a 1.0.3 però.

Issue

Related commit

0

Nel tuo caso non è il pagerfanta che fa le sottoquery. È la fonte da cui proviene l'istanza del generatore di query.

Di solito ho una funzione nel repository di entità che restituisce un'istanza di generatore di query semplice invece dei risultati. Ti è stato permesso di scrivere un efficiente generatore di query. Quindi inserisco il generatore di query in DoctrineORMAdapter.

Ho questo funzione di supporto che uso durante i miei progetti:

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

Si può passare un array, entità o un'istanza di generatore di query e restituisce un oggetto opportunamente impaginati pronti per l'uso.

Probabilmente sapete come è fatto, ma in ogni caso, ecco quello che ho nel mio repository entità - una funzione restituisce esempio generatore di query (perfetto per pagerfanta), gli altri restituisce un array da utilizzare altrove:

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

Poi si combinano quei due per ottenere l'oggetto vero e proprio cercapersone:

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

Io non la penso così.Il mio queryBuilder (quello che passo all'adattatore) è responsabile solo della query di selezione originale. Il modo in cui Pagerfanta crea la query 'COUNT' da esso è una cosa diversa. – yefrem

+0

Siamo spiacenti, ho controllato le mie query di impaginazione e sembrano fare lo stesso tipo di sottotagery. Ma poi non ho notato alcuna relazione tra prestazioni e il conteggio dei record sulla mia applicazione. Avrei pensato che l'ottimizzatore mysql avrebbe limitato il set di risultati interni anche in questi casi. Interessante. –

+0

Ho notato problemi quando il mio tavolo ha raggiunto centinaia di migliaia di record. Aveva anche un alto tasso di aggiornamento, quindi penso che il risultato non possa essere memorizzato nella cache. Su altri siti ho circa 2000 oggetti paginati e aggiornati una volta al giorno e non vedo problemi – yefrem