【问题标题】:loading of related documents in doctrine ODM leads to too many queries在学说ODM中加载相关文档导致查询过多
【发布时间】:2019-03-14 11:12:16
【问题描述】:

我一直在尝试减少 Web api 上的数据库查询数量。 我的数据库有 3 个集合:playgroundwidgettoken

一个游乐场有很多小部件,一个小部件有一个令牌。每个关系使用referencesOne/referenceMany

这是我的简化模型

/**
 * @MongoDB\Document()
 */
class Widget
{
    /**
     * @MongoDB\ReferenceOne(targetDocument="Token", inversedBy="widgets")
     */
    protected $token;

    /**
     * @MongoDB\ReferenceOne(targetDocument="Playground", inversedBy="widgets")
     */
    protected $playground;
}

/**
 * @MongoDB\Document()
 */
class Playground
{
    /**
     * @MongoDB\ReferenceMany(targetDocument="Widget", mappedBy="playground")
     */
    protected $widgets;
}

/**
 * @MongoDB\Document()
 */
class Token
{
    /**
     * @MongoDB\ReferenceMany(targetDocument="Widget", mappedBy="token")
     */
    protected $widgets;
}

我需要使用完整的游乐场及其所有小部件和令牌,但默认情况下,Doctrine 会执行太多查询:一个获取游乐场(ok),一个获取映射的所有小部件(ok)和每个小部件,一个查询来获取令牌(不好)。有没有办法一次查询所有令牌而不是一个一个地获取它们?

我看过 prime,但它似乎没有解决我的问题...

除了使用查询生成器并手动对所有对象进行水合以减少查询计数之外,还有其他方法吗?

编辑: 正如我在评论中添加的那样,我正在寻找的是将游乐场及其所有依赖项作为一个大对象,对其进行 json 编码并将其返回到响应中。

我现在要做的是查询 Playground 并对其进行编码,但 Doctrine 以一种非有效的方式填充依赖项:首先是获取 playgroung 的查询,然后,还有一个获取相关小部件的查询是对每个小部件进行一次查询以获取其令牌。

由于一个 Playground 可以有数百个小部件,这会导致数百个数据库查询。

我正在寻找一种方法来告诉 Doctrine 仅使用 3 个查询(一个用于获取 playgroung,一个用于获取小部件,一个用于获取令牌)来获取所有这些数据。

【问题讨论】:

  • 我不太明白这里的问题。您想要所有Token 在您的数据库中吗?您可以使用$tokenRepository->findAll(); 将所有Token$tokenRepository->findBy(array $criteria);$criteria 一起作为您的查询参数。
  • @Etshy 不,我不是在寻找关于令牌的 findAll,我想要一个游乐场及其所有依赖项并将文档作为 json 返回。 Doctrine 没有以一种有效的方式查询数据库:它所做的是 1)获取游乐场,2)获取相关的小部件,3)对于每个小部件,获取相关的令牌。由于一个 Playground 可以有数百个小部件,这会导致数百个数据库查询。我正在寻找一种方法来告诉教义循环遍历所有游乐场小部件以获取相关的令牌 ID,然后一次获取所有令牌。这样无论有多少小部件,总会有 3 个查询。
  • 哦,好的。我不确定你怎么能做到这一点。您可以尝试findBy(['widgets.id' => $widgetsIds]) 之类的方法,其中$widgetsIds 是一个ID 数组。我真的不确定findBy 是否可以使用这样的数组。
  • 你考虑过使用aggregate吗?因为在我看来,你所需要的只是一个聚合管道。
  • 简单地编写一个自定义查询可能更简单,性能更高。

标签: php mongodb symfony doctrine-odm


【解决方案1】:

更新:由于$playground 中的 ArrayCollection 至少应包含所有小部件作为代理对象(或应在访问时加载),因此以下内容应该可以获取所有必需的令牌。 .

由于文档管理器保留所有托管对象,它应该防止发生额外的查询。 (注意execute 中省略的赋值)。

$qb = $dm->createQueryBuilder('Token')->findBy(['widget' => $playground->getWidgets()]);
$qb->getQuery()->execute();

灵感来自this page on how to avoid doctrine orm traps - point 5

旧/原始答案

我不太熟悉 mongodb,tbh,但根据教义的 priming references,您应该能够通过以下方式轻松地为您的游乐场补充水分:

$qb = $dm->createQueryBuilder('Widget')->findBy(['playground' => $playground]);
$qb->field('token')->prime(true);
$widgets = $qb->getQuery()->execute();

但是,我可能错了。

【讨论】:

  • 我已经尝试过了,但它不起作用,因为我使用的是关系的反面
  • @ᴄʀᴏᴢᴇᴛ 这可能是一个愚蠢的问题:mongodb 中的 Widget 不包含操场和令牌的 id?我想知道我的逻辑反转是否有效(见更新)。
  • 我读你的答案太快了,是的,我可以从小部件中初始化令牌(小部件具有令牌 ID)。这或多或少是我最终做的事情,但我不太喜欢这个解决方案,因为有了这个,我在一个 var 中有操场,在另一个 var 中有小部件 + 令牌。所以我不能使用$playground->getWidgets()(否则学说将运行数据库查询,因为没有结果缓存)或者我必须手动将小部件设置到操场上,我认为这不是一个好习惯......无论如何,似乎没有更好的选择......也许,问题出在数据库设计中
  • 这将是需要的查询:$playgroundRepository->createQueryBuilder()->field('widgets.token')->prime()->field('id')->equals($playgroundId); 但不幸的是,我无法建立深层关系
  • @ᴄʀᴏᴢᴇᴛ 我相信你低估了 DocumentManager。我会说,当您已经加载了所有令牌时,它将阻止“n+1”查询的发生。请检查(通过$playground->getWidgets() 然后$widget->getToken()
【解决方案2】:

那又怎样:

class Foo
{
    /** @var \Doctrine\ORM\EntityManagerInterface */
    private $entityManager;

    public function __construct(\Doctrine\ORM\EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function getAll(): array {
        $qb = $this->entityManager->createQueryBuilder();
        return $qb
            ->select('p,t,w')
            ->from(Playground::class, 'p')
            ->join(Widget::class, 'w')
            ->join(Token::class, 't')
            ->getQuery()
            ->getResult();
    }
}

至少使用 mysql 后端,这解决了“n+1 问题”。不过,不确定 MongoDB。

【讨论】:

  • 很遗憾,mongodb中没有join
猜你喜欢
  • 2016-12-21
  • 2014-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多