这与之前的问题相关:Doctrine/Symfony query builder add select on left join
我想使用Doctrine ORM执行复杂的连接查询。我想选择10个分页博客帖子,左边加入一个作者,比如当前用户的值,以及帖子上的主题标签。我的查询构建器如下所示:
$query = $em->createQueryBuilder()
->select('p')
->from('Post', 'p')
->leftJoin('p.author', 'a')
->leftJoin('p.hashtags', 'h')
->leftJoin('p.likes', 'l', 'WITH', 'l.post_id = p.id AND l.user_id = 10')
->where("p.foo = bar")
->addSelect('a AS post_author')
->addSelect('l AS post_liked')
->addSelect('h AS post_hashtags')
->orderBy('p.time', 'DESC')
->setFirstResult(0)
->setMaxResults(10);
// FAILS - because left joined hashtag collection breaks LIMITS
$result = $query->getQuery()->getResult();
// WORKS - but is extremely slow (count($result) shows over 80,000 rows)
$result = new \Doctrine\ORM\Tools\Pagination\Paginator($query, true);
奇怪的是,paginator上的count($ result)显示了我的表中的总行数(超过80,000),但是正如预期的那样遍历带有foreach输出10 post实体的$ result。我是否需要进行一些额外配置才能正确限制我的分页器?
如果这是paginator类的限制,我还有其他选择吗?编写自定义分页器代码或其他分页器库?
(奖金):如何给阵列加水,比如$ query-> getQuery() - > getArrayResult();?
编辑:我在我的函数中遗漏了一个错误的命令。看起来包括groupBy和orderBy会导致减速(使用groupBy而不是paginator)。如果我省略其中一个,查询速度很快。我尝试在"时间"上添加一个索引。在我的表中列,但没有看到任何改进。我尝试的事情
// works, but makes the query about 50x slower
$query->groupBy('p.id');
$result = $query->getQuery()->getArrayResult();
// adding an index on the time column (no improvement)
indexes:
time_idx:
columns: [ time ]
// the above two solutions don't work because MySQL ORDER BY
// ignores indexes if GROUP BY is used on a different column
// e.g. "ORDER BY p.time GROUP BY p.id is" slow
答案 0 :(得分:4)
您应该简化查询。这样可以节省一些执行时间。我无法测试您的查询,但这里有一些指示:
我使用KnpLabs/KnpPaginatorBundle并且还可以解决复杂查询的速度问题。
通常使用LIMIT x,z对于DB来说很慢,因为它在整个数据集上运行COUNT。如果没有使用索引,那就太慢了。
您可以使用不同的方法并通过ID推进进行一些自定义分页,但这会使您的方法复杂化。我已经将它用于像SYSLOG表这样的大型数据集。但是你放弃了排序和总记录数功能。
答案 1 :(得分:0)
在一天结束时,我的应用程序中使用的许多查询都太复杂而无法正确使用Paginator,而且我无法使用Paginator进行阵列水化模式。
根据MySQL documentation,如果GROUP BY用于其他列,则无法通过索引解析ORDER BY。因此,我最终使用了几个后处理查询来填充我的基本结果(ORDERed和LIMITed)与一对多关系(如hashtags)。
对于从连接表加载单行的连接,我能够在基本有序查询中加入所需的值。例如,在加载" like状态"对于当前用户,只需要加载喜欢该喜欢的一组用户以指示当前帖子是否已被喜欢。同样,给定帖子只有一位作者的存在会产生一个连接的作者行。 e.g。
$query = $em->createQueryBuilder()
->select('p')
->from('Post', 'p')
->leftJoin('p.author', 'a')
->leftJoin('p.likes', 'l', 'WITH', 'l.post_id = p.id AND l.user_id = 10')
->where("p.foo = bar")
->addSelect('a AS post_author')
->addSelect('l AS post_liked')
->orderBy('p.time', 'DESC')
->setFirstResult(0)
->setMaxResults(10);
// SUCCEEDS - because joins only join a single author and single like
// no collections are joined, so LIMIT applies only the the posts, as intended
$result = $query->getQuery()->getArrayResult();
这会产生以下形式的结果:
[
[0] => [
['id'] => 1
['text'] => 'foo',
['author'] => [
['id'] => 10,
['username'] => 'username',
],
['likes'] => [
[0] => [
['post_id'] => 1,
['user_id'] => 10,
]
],
],
[1] => [...],
...
[9] => [...]
]
然后在第二个查询中,我加载了上一个查询中加载的帖子的主题标签。 e.g。
// we don't care about orders or limits here, we just want all the hashtags
$query = $em->createQueryBuilder()
->select('p, h')
->from('Post', 'p')
->leftJoin('p.hashtags', 'h')
->where("p.id IN :post_ids")
->setParameter('post_ids', $pids);
产生以下内容:
[
[0] => [
['id'] => 1
['text'] => 'foo',
['hashtags'] => [
[0] => [
['id'] => 1,
['name'] => '#foo',
],
[2] => [
['id'] => 2,
['name'] => '#bar',
],
...
],
],
...
]
然后我只是遍历包含主题标签的结果并将它们附加到原始(有序和有限)结果。这种方法最终会更快(即使它使用更多查询),因为它避免了GROUP BY和COUNT,充分利用了MySQL索引,并允许更复杂的查询,例如我发布的here。
答案 2 :(得分:0)
您可以通过执行以下一项或多项优化,将paginator
配置为使用更简单的'count'
sql策略。
$paginator = new Paginator($query, false);
$paginator->setUseOutputWalkers(false);
如果结果出乎意料,则可能需要DISTINCT
选择(select('DISTINCT p'))
对我们来说,它进行了重大改进,因此我们无需编写或使用自定义paginator
。
可以在this site上找到更多详细信息。请注意,我是该网站的所有者。