6

Symfony2 と Silex で Pagerfanta と Doctrine アダプターを使用しています。データベースが大きくなるにつれて、ページネーションで大きなデータを表示する管理統計ページに大きな負荷がかかることに気付きました。プロファイラーをチェックしたところ、信じられないほど非効率的なクエリが表示されました。

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;`

最初のクエリは、固定バージョンのDoctrineORMAdapterクラスを作成することで簡単に修正できました。クエリを生成するコードCOUNT()はより複雑なので、これに対する解決策があるかどうかを尋ねることにしました。

Pagerfanta がネストされたクエリを実行しないようにする方法はありますか?

4

2 に答える 2

0

あなたの場合、サブクエリを実行するのは pagerfanta ではありません。これは、元のクエリ ビルダー インスタンスのソースです。

通常、エンティティ リポジトリには、結果の代わりにプレーンなクエリ ビルダー インスタンスを返す関数があります。効率的なクエリ ビルダを作成するのはあなたに任されています。次に、そのクエリ ビルダーを DoctrineORMAdapter にフィードします。

プロジェクト全体で使用するこのヘルパー関数があります。

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

配列、エンティティ、またはクエリ ビルダー インスタンスを渡すと、適切にページ分割された、すぐに使用できるオブジェクトが返されます。

あなたはおそらくそれがどのように行われたか知っているでしょうが、とにかく、これが私のエンティティリポジトリにあるものです.1つの関数はクエリビルダインスタンスを返し(pagerfantaに最適)、もう1つの関数は他の場所で使用される配列を返します:

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

次に、これら 2 つを組み合わせて実際のページャー オブジェクトを取得します。

$singleSmsPager = $this->pagerUtil->paginate(
    $this->em->getRepository('TreasureForgeMessageBundle:Message')
        ->getMessageQueryBuilder(CcToolSender::CAMPAIGN_ID, array(), array('u.username' => 'admin')),
    array(
        'curPage' => $singleSmsPage,
        'perPage' => 10
    )
);
于 2015-02-17T12:40:45.940 に答える