Doctrine 在水合作用期间添加了额外的查询,导致 "normal" 一对一和 self-referenced 关系的 n+1 问题
Doctrine adds extra queries during hydration causing n+1 problem with "normal" one to one and self-referenced relations
News
使用 one-to-many self-referencing 方法相互关联(一个消息是 parent 并且可以有多个 children)。更重要的是,每个 News
与 Event
和 Gallery
具有正常的(非 self-referenced)一对一关系。当我 运行 简单 DQL:
SELECT n FROM App\Entity\News n WHERE n.parent = :id
然后通过 getResults
方法与默认 HYDRATION_OBJECT
值集合并结果,在 getResults
方法内部的某处进行额外查询。
SELECT t0.* FROM event t0 WHERE t0.news_id = 2 AND ((t0.deleted_at IS NULL));
SELECT t0.* FROM gallery t0 WHERE t0.news_id = 2 AND ((t0.deleted_at IS NULL));
SELECT t0.* FROM event t0 WHERE t0.news_id = 1 AND ((t0.deleted_at IS NULL));
SELECT t0.* FROM gallery t0 WHERE t0.news_id = 1 AND ((t0.deleted_at IS NULL));
其中news_id = 1
和news_id = 2
是第一个查询选择的children条新闻。
News
也没有 self-referenced 一对多关系(我在这里忽略了它们),但是 hydration 没有对它们进行额外的查询。只有在 where
语句中涉及 parent
关系时才会出现此问题。
如何重现
// news Entity
/**
* @ORM\Entity(repositoryClass="App\Repository\NewsRepository")
* @ORM\Table(uniqueConstraints={@UniqueConstraint(name="news_slug_deleted", columns={"slug","deleted_at"})})
*/
class News {
use SoftDeleteableEntity;
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $title;
/**
* @Gedmo\Slug(fields={"title"})
* @ORM\Column(length=128)
*/
private $slug;
/**
* @ORM\OneToOne(targetEntity="App\Entity\Gallery", mappedBy="news", cascade={"persist", "remove"}, orphanRemoval=true)
*
* @var Gallery
*/
private $gallery;
/**
* @ORM\OneToOne(targetEntity="App\Entity\Event", mappedBy="news", cascade={"persist", "remove"}, orphanRemoval=true)
*
* @var Event
*/
private $event;
/**
* One News has Many News.
* @ORM\OneToMany(targetEntity="News", mappedBy="parent")
*/
private $children;
/**
* Many News have One News.
* @ORM\ManyToOne(targetEntity="News", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
*/
private $parent;
}
/**
* @ORM\Entity(repositoryClass="App\Repository\EventRepository")
* @Gedmo\SoftDeleteable()
*/
class Event
{
use SoftDeleteableEntity;
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity="App\Entity\News", inversedBy="event", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $news;
Gallery
实体与 Event
类似,所以我在这里忽略它。
// News controller
public function index(NewsRepository $newsRepository, $slug)
{
$news = $newsRepository->findOneBy(['slug' => $slug]);
$newsRepository->getConnectedNews($news->getId());
}
// news repository
class NewsRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, News::class);
}
public function getConnectedNews($newsId) {
$query = $this->createQueryBuilder('n')->andWhere('n.parent = :id')->setParameter('id', $newsId);
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_OBJECT);
}
}
news
的 Hydration 有 20 children 最终得到:20 * 2 + 1 (n*r+1) 个查询,其中:
- (n) 20是children
的个数
- (r)2是一对一关系的个数
- 1 是基本查询
我想防止 Doctrine 对一对一关系进行额外的查询。是 Doctrine bug 还是不想要的行为,还是我在某处犯了错误?
总而言之,我只想获取所有 self-referenced children 没有 one-to-one 关系,因为我没有要求检索它们,所以它应该只使用一个查询来获取所有children news
无需对检索到的每个 'news' object 进行额外查询,并且每个都是一对一的关系。
您在该实体中有一对反向一对一关系。
反向 OneToOne 关系不能被 Doctrine 延迟加载,并且很容易成为性能问题。
如果您真的需要将这些关系映射到反向端(而不仅仅是在拥有端)确保显式进行适当的连接,或将这些关联标记为 FETCH=EAGER
,以便 Doctrine 为您进行连接。
例如避免可怕的“n+1”问题的查询是:
SELECT n, g, e
FROM App\Entity\News n
LEFT JOIN n.gallery g
LEFT JOIN n.event e
WHERE n.parent = :id
News
使用 one-to-many self-referencing 方法相互关联(一个消息是 parent 并且可以有多个 children)。更重要的是,每个 News
与 Event
和 Gallery
具有正常的(非 self-referenced)一对一关系。当我 运行 简单 DQL:
SELECT n FROM App\Entity\News n WHERE n.parent = :id
然后通过 getResults
方法与默认 HYDRATION_OBJECT
值集合并结果,在 getResults
方法内部的某处进行额外查询。
SELECT t0.* FROM event t0 WHERE t0.news_id = 2 AND ((t0.deleted_at IS NULL));
SELECT t0.* FROM gallery t0 WHERE t0.news_id = 2 AND ((t0.deleted_at IS NULL));
SELECT t0.* FROM event t0 WHERE t0.news_id = 1 AND ((t0.deleted_at IS NULL));
SELECT t0.* FROM gallery t0 WHERE t0.news_id = 1 AND ((t0.deleted_at IS NULL));
其中news_id = 1
和news_id = 2
是第一个查询选择的children条新闻。
News
也没有 self-referenced 一对多关系(我在这里忽略了它们),但是 hydration 没有对它们进行额外的查询。只有在 where
语句中涉及 parent
关系时才会出现此问题。
如何重现
// news Entity
/**
* @ORM\Entity(repositoryClass="App\Repository\NewsRepository")
* @ORM\Table(uniqueConstraints={@UniqueConstraint(name="news_slug_deleted", columns={"slug","deleted_at"})})
*/
class News {
use SoftDeleteableEntity;
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $title;
/**
* @Gedmo\Slug(fields={"title"})
* @ORM\Column(length=128)
*/
private $slug;
/**
* @ORM\OneToOne(targetEntity="App\Entity\Gallery", mappedBy="news", cascade={"persist", "remove"}, orphanRemoval=true)
*
* @var Gallery
*/
private $gallery;
/**
* @ORM\OneToOne(targetEntity="App\Entity\Event", mappedBy="news", cascade={"persist", "remove"}, orphanRemoval=true)
*
* @var Event
*/
private $event;
/**
* One News has Many News.
* @ORM\OneToMany(targetEntity="News", mappedBy="parent")
*/
private $children;
/**
* Many News have One News.
* @ORM\ManyToOne(targetEntity="News", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
*/
private $parent;
}
/**
* @ORM\Entity(repositoryClass="App\Repository\EventRepository")
* @Gedmo\SoftDeleteable()
*/
class Event
{
use SoftDeleteableEntity;
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity="App\Entity\News", inversedBy="event", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $news;
Gallery
实体与 Event
类似,所以我在这里忽略它。
// News controller
public function index(NewsRepository $newsRepository, $slug)
{
$news = $newsRepository->findOneBy(['slug' => $slug]);
$newsRepository->getConnectedNews($news->getId());
}
// news repository
class NewsRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, News::class);
}
public function getConnectedNews($newsId) {
$query = $this->createQueryBuilder('n')->andWhere('n.parent = :id')->setParameter('id', $newsId);
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_OBJECT);
}
}
news
的 Hydration 有 20 children 最终得到:20 * 2 + 1 (n*r+1) 个查询,其中:
- (n) 20是children 的个数
- (r)2是一对一关系的个数
- 1 是基本查询
我想防止 Doctrine 对一对一关系进行额外的查询。是 Doctrine bug 还是不想要的行为,还是我在某处犯了错误?
总而言之,我只想获取所有 self-referenced children 没有 one-to-one 关系,因为我没有要求检索它们,所以它应该只使用一个查询来获取所有children news
无需对检索到的每个 'news' object 进行额外查询,并且每个都是一对一的关系。
您在该实体中有一对反向一对一关系。
反向 OneToOne 关系不能被 Doctrine 延迟加载,并且很容易成为性能问题。
如果您真的需要将这些关系映射到反向端(而不仅仅是在拥有端)确保显式进行适当的连接,或将这些关联标记为 FETCH=EAGER
,以便 Doctrine 为您进行连接。
例如避免可怕的“n+1”问题的查询是:
SELECT n, g, e
FROM App\Entity\News n
LEFT JOIN n.gallery g
LEFT JOIN n.event e
WHERE n.parent = :id