Symfony / doctrine:优化n + 1个查询

时间:2016-05-28 10:23:48

标签: sql symfony doctrine eager-loading

上下文

想象一组提案,每个用户都可以为提案投票或者投票。

所以我有2个模特:

  • A Proposal
  • Vote,与proposaluseropinion(向上或向下)相关。

现在,我想显示所有提案,包括每个提案的额外信息:

  • 有多少赞成票?
  • 有多少downvotes?
  • 当前用户是否赞成投票?
  • 当前用户是否已投票?

问题

这很容易实现,但这意味着每个提案显示4个请求(称为n + 1查询问题)。 我来自Rails世界,可以使用急切加载或一些棘手的查询轻松解决这个问题。但我不能用学说来解决它,需要一些帮助!

我尝试了什么

使用魔法字段

第一个解决方案是进行自定义查询:选择所有提案,包括upvotes,downvotes的数量,让当前用户上调或下调。

现在,查询会返回比模型描述的列多4列:count_upvotescount_downvoteshas_user_upvotedhas_user_downvoted

要与学说一起使用,我必须将这些字段添加到我的Proposal实体。

对我来说,这不是一个好的解决方案,因为它意味着始终使用此查询来避免混蛋模型,其中这些字段可以为空。

使用摘要

第二种解决方案是使视图使用另一个对象。使用参数创建此对象:当前用户。此对象由优化查询创建,并包含以下方法:

  • getAllProposals()
  • getUpvotes($a_proposal)
  • getDownvotes($a_proposal)
  • hasUserUpvoted($a_proposal)
  • hasUserDownvoted($a_proposal)

对我而言,被迫为视图优化创建一个对象真的太过分了。

使用扩展实体

此第三个解决方案使用一个唯一的查询来获取提案,他们的upvotes,downvotes,并检查用户是否已经上升或下调。

它创建了一个新的“扩展”实体,

public function __construct(
    Proposal $badgeProposal,
    $upvotesCount,
    $downvotesCount,
    $hasUserUpvoted,
    $hasUserDownvoted
) {

对我来说,这是更好的解决方案,但目前这不起作用,因为我不知道如何简单地从SQL行水合一个Proposal(但我几乎没有搜索)。

轮到你了

那么,其他什么解决方案呢?对于学说开发者来说,必须是一个众所周知的问题。

1 个答案:

答案 0 :(得分:4)

您是否已经对此进行过实验或只考虑过它?

对我来说,一个简单的fetch="EAGER"解决了这个问题,导致只有一个查询。使用以下设置

class Proposal {
    /**
     * @ORM\OneToMany(targetEntity="Vote", mappedBy="proposals", fetch="EAGER")
     */
    protected $votes;
}

class Vote {
    /**
     * @ORM\ManyToOne(targetEntity="Proposal", inversedBy="votes")
     * @ORM\JoinColumn(..)
     */
    protected $proposal;
    /**
     * @ORM\ManyToOne(targetEntity="Opinion", inversedBy="votes")
     * @ORM\JoinColumn(..)
     */
    protected $opinion;
    /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="votes")
     * @ORM\JoinColumn(..)
     */
    protected $user;
}

class Opinion/User {
    /**
     * @ORM\OneToMany(targetEntity="Vote", mappedBy="proposals")
     */
    protected $votes;
}

实际上,我对Proposal实体进行了更多编辑,添加了以下方法

class Proposal {
    public function getCountOpinion($id) {
        $count = 0;
        foreach ($this->getVotes() as $vote) {
            if ($vote->getOpinion()->getId() === $id) {
                $count++;
            }
        }
        return $count;
    }

    public function getUserVote($user) {
        foreach ($this->getVotes() as $vote) {
            if ($vote->getUser() == $user) {
                return $vote;
            }
        }
        return null;
    }
}

正如我在开头提到的,这仍然只导致一个查询。但是,您在此处看到了很多for。如果你对每个提案有很多投票,你可能想要缓存结果(例如,只迭代一次投票,得到所有的countOpinion和userVote)

PS:不要忘记为具有ArrayCollections(OneToMany)的类添加构造函数