Symfony4 - Doctrine 分页计数查询性能低下(634484 条记录)

nag*_*dev 5 mysql sql doctrine symfony symfony4

我有一个需要分页的表(没有关系)。为此,我使用 Doctrine 分页工具。

该表包含 634484 条记录。Doctrine 执行以下查询:

SELECT p0_.id AS id_0, p0_.name AS name_1, p0_.level AS level_2, p0_.alignment AS alignment_3, p0_.account_id AS account_id_4 FROM player.player p0_ ORDER BY p0_.level DESC LIMIT 25;
Run Code Online (Sandbox Code Playgroud)

解释:

id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   SIMPLE  p0_     index       search  1       25  100.00
Run Code Online (Sandbox Code Playgroud)

执行时间:1.04 ms

有问题的查询

SELECT 
  COUNT(*) AS dctrn_count 
FROM 
  (
    SELECT 
      DISTINCT id_0 
    FROM 
      (
        SELECT 
          p0_.id AS id_0, 
          p0_.name AS name_1, 
          p0_.level AS level_2, 
          p0_.alignment AS alignment_3 
        FROM 
          player.player p0_ 
        ORDER BY 
          p0_.level DESC
      ) dctrn_result
  ) dctrn_table
Run Code Online (Sandbox Code Playgroud)

解释:

id  select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
1   PRIMARY <derived2>      ALL                 634484  100.00  
2   DERIVED p0_     ALL PRIMARY             634484  100.00  Using temporary
Run Code Online (Sandbox Code Playgroud)

执行时间:2414.72 ms

Doctrine 使用 DISTINCT 关键字来找出表包含多少行,这可能是查询运行这么长时间的原因。

没有 DISTINCT 关键字的执行时间:1.26 ms

编辑

存储库:

public function findLatestPaginated(int $page = 1): array
{
    $queryBuilder = $this->createQueryBuilder('c')
        ->addOrderBy('c.level', 'DESC');


    return $this->paginate($queryBuilder, $page, Character::PER_PAGE);
}
Run Code Online (Sandbox Code Playgroud)

分页方法:

<?php

namespace App\Core\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository as BaseServiceEntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;

abstract class ServiceEntityRepository extends BaseServiceEntityRepository
{
    protected function paginate(QueryBuilder $queryBuilder, int $currentPage, int $perPage)
    {
        $currentPage = $currentPage < 1 ? 1 : $currentPage;
        $firstResult = ($currentPage - 1) * $perPage;

        /** @var Query $query */
        $query = $queryBuilder
            ->setFirstResult($firstResult)
            ->setMaxResults($perPage)
            ->getQuery();

        $paginator = new Paginator($query, false);
        $numResults = $paginator->count();
        $hasPreviousPage = $currentPage > 1;
        $hasNextPage = ($currentPage * $perPage) < $numResults;

        return [
            'result' => $paginator->getIterator(),
            'currentPage' => $currentPage,
            'hasPreviousPage' => $hasPreviousPage,
            'hasNextPage' => $hasNextPage,
            'previousPage' => $hasPreviousPage ? $currentPage - 1 : null,
            'nextPage' => $hasNextPage ? $currentPage + 1 : null,
            'numPages' => (int)ceil($numResults / $perPage),
            'haveToPaginate' => $numResults > $perPage,
        ];
    }
}
Run Code Online (Sandbox Code Playgroud)

问题:

  • 为什么Doctrine在计算查询中使用distinct关键字?(id这是主键)
  • 我怎样才能防止这种情况发生?

Jan*_*tis 5

您可以通过执行以下操作从计数查询中禁用DISTINCT关键字:

use Doctrine\ORM\Tools\Pagination\CountWalker;

$query->setHint(CountWalker::HINT_DISTINCT, false);
Run Code Online (Sandbox Code Playgroud)

看看Doctrine/ORM/Tools/Pagination/Paginator代码,这似乎会阻止将重复语句添加到计数查询中。