我遇到了一个奇怪的问题,不明白为什么会发生这种情况。
我有一个实体在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 - 0000000f37d2| 147605 | 890467 | 1 |
| 11000000 - 0000 - 0000 - 0000000e065f| 890467 |null| 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
作为复合键可以停止这种行为,但是我们需要它以这种格式工作,所以它不是一个选项。
有什么想法可能会导致这个额外的查询,以及如何摆脱它,所以它将按预期工作?
1条答案
按热度按时间dxxyhpgq1#
好的,看起来发生的是这样的。当订阅被加载时,在订阅的水合过程中(将数据转换为对象),遇到了多对一关系,除非另有说明,否则它将被 * 急切地加载 *,即立即加载。这导致了第二个查询,这在某种程度上可能非常有用。在这里,同样的事情重复。
这与另一个问题相吻合,即关联关系定义在
parent_int_id
和总是非空的instance_id
上。(仅instance_id
)(可能相关的教义来源-其中有一部分是TODO,所以也许它还没有像预期的那样工作)。这显然不是你想要的。由于所有父实体都是一个接一个地获取的,所以可能有很好的理由不急于获取它,但是lazily。如果您获取现有实体,这将阻止致命查询的发生。但是,一旦您访问没有父实体的订阅上的父对象,致命查询就会再次发生。
一般来说,可能有办法防止这种情况发生,但是,我不清楚这是否可以在理论上实现唯一“正确”的方法可能是以某种方式教导这样的原则:如果父组合键的一部分为空,则不应尝试获取父组合键(见链接代码,它在那里的某个地方),这可能涉及到修改UnitOfWork的默认实现.创建您自己的使用新UnitOfWork的AlternityManager。您必须调整您的实现以保持与教义的同步。由于这种定制很少需要,正确的方式可能会太麻烦。
根据您的软件开发标准和要求,我可以想象一些选项,其中一些或所有选项可能不适合。然而,这里有一些:
1.对于
parent_id
,你可以引用uuid列而不是复合键(这显然需要uuid列的索引,它应该已经有了,因为它是一个唯一的列)-这可能是最安全的方法,它会工作。1.你可以延迟加载父实体,并在你的父getter中确保你永远不会尝试访问非id列,否则它会尝试获取对象。(这可能会触发致命的查询)。您必须检查父节点是否具有rid(除了instance_id),否则返回null。-我很遗憾地不知道,如果这真的有用,延迟加载的代理对象通常为id列提供可用的getter,但我从未尝试过使用复合键。
1.添加一个
parent_instance_id
列,这在技术上是冗余的,并会带来自己的问题,但它会工作,因为当不存在父列时,它可以为null。1.对于这个特定的父关系,根本不使用原则关联Map。相反,将其保留为属性,并在请求时从存储库中获取父关系(通过id)(从技术上讲,这是选项2的降级,但它更容易控制,但关注点分离是肮脏的,因为实体不应该进行查询或从存储库中获取内容)