我遇到了一个奇怪的问题,无法理解为什么会发生这种情况。
我有一个实体在
instance
和 rid
字段上使用组合主键。
我也有
parent
作为与同一实体的自引用关系。
class Subscription
{
/**
* @ORM\Column(name="id", type="uuid", unique=true)
* @JMS\Type("string")
*/
protected $id;
/**
* @ORM\Id
* @ORM\Column(name="instance_id", type="integer")
* @JMS\Type("int")
*/
protected $instance;
/**
* @ORM\Id
* @ORM\Column(name="rid", type="bigint")
* @ORM\SequenceGenerator(sequenceName="subscriptions_id_seq", initialValue=1, allocationSize=1)
* @JMS\Type("int")
*/
protected $rid;
/**
* @ORM\ManyToOne(targetEntity="Subscription")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="parent_int_id", referencedColumnName="rid"),
* @ORM\JoinColumn(name="instance_id", referencedColumnName="instance_id")
* })
*/
protected $parent;
...
}
我面临的问题:
对于以下数据:
id | 摆脱 | parent_int_id | 实例_id |
---|---|---|---|
11000000-0000-0000-0000-0000000f37d2 | 147605 | 890467 | 1 |
11000000-0000-0000-0000-0000000e065f | 890467 | 空 | 1 |
$subscription = $subscriptionService->findOneBy([
'rid' => 147605
]);
使用
findOneBy()
检索订阅时,我看到 Doctrine 触发了 3 个查询:
这是初始查询,按预期工作:
SELECT t0.id AS id_1, t0.instance_id AS instance_id_2, t0.rid AS rid_3, ...
FROM subscriptions t0 WHERE t0.rid = 147605 LIMIT 1;
这是对父字段的查询,也按预期工作:
SELECT t0.id AS id_1, t0.instance_id AS instance_id_2, t0.rid AS rid_3, ...
FROM subscriptions t0 WHERE t0.rid = '890467' AND t0.instance_id = 1 LIMIT 1;
此查询是意外的,无法找到禁用它的方法或了解触发它的原因:
SELECT t0.id AS id_1, t0.instance_id AS instance_id_2, t0.rid AS rid_3, ...
FROM subscriptions t0 WHERE t0.instance_id = 1
如您所见,第三个查询尝试选择该
instance_id
的所有行,这是一个问题,因为我们的表非常大(数百万行),这会导致我们的数据库崩溃。检索到的对象已正确构建,并且 parent
属性已按预期填充,但我希望该查询根本不存在。
删除
instance
作为复合键可以停止此行为,但是我们需要它以这种格式工作,所以这不是一个选项。
知道什么可能导致这个额外的查询以及如何摆脱它,以便它按预期工作吗?
好吧,看起来发生的事情是这样的。加载订阅时,在该订阅的水合作用(将数据转换为对象)期间,会遇到多对一关系,除非另有说明,否则将“热切加载”,即立即加载。这会导致第二个查询,这在某种程度上可能非常有用。在这里,同样的事情重复发生。 这与另一个问题相吻合,即关联关系是在
parent_int_id
和始终非空的
instance_id
上定义的。这足以触发对父实体的搜索,并具有简化的条件(仅instance_id
)(可能相关的教义来源- 其中有一个带有 TODO 的部分,所以也许它还没有按预期工作)。这显然不是您想要的。 由于无论如何,所有父实体都是一一获取的,因此可能有充分的理由不急切地获取它,而是“懒惰”地获取它。如果您获取现有实体,这将阻止致命查询的发生。 但是,一旦您访问没有父对象的订阅上的父对象,致命的查询就会再次发生。
可能有一些方法可以防止这种情况发生,但是,我不清楚这是否可以在不深入研究的情况下在理论(或其注释/属性)中实现。唯一的“正确”方法可能是以某种方式教导教条,如果父复合键的一部分为空,则它不应该尝试获取父项(请参阅链接代码,它在某处),这可能涉及覆盖默认实现UnitOfWork 的...这对于 ORM 工作原理非常核心,显然不打算被覆盖。您可能必须编写自己的 UnitOfWork,创建您自己的使用新 UnitOfWork 的 EntityManager。您必须调整您的实现,以在一定程度上与原则保持同步。由于这种定制很少需要,正确的方法可能会很麻烦。 我可以想象一些选项,根据您的软件开发标准和要求,这些选项中的部分或全部可能不合适。不过,这里有一些:
对于
parent_id
parent_instance_id