Django的多对多关系通过表,如何防止它做很多查询?



我正在为我有活动的D&D工具开发API,人们可以成为活动的成员。我需要为活动的每个成员存储额外的信息,所以我使用through模型。

class Campaign(models.Model):
name = models.CharField(max_length=50)
description = models.TextField()
owner = models.ForeignKey(User, related_name='owned_campaigns', on_delete=models.CASCADE)
members = models.ManyToManyField(User, related_name='campaigns', through='Membership')

class Membership(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
is_dm = models.BooleanField(default=False)

如果我想获取用户所属的所有活动,我可以简单地这样做:

>>> from auth.models import User
>>> user = User.objects.get(pk=1)
>>> campaigns = user.campaigns.select_related('owner')
>>> print(campaign)

这做了一个INNER JOIN来获取活动的所有者,这样就可以避免做额外的查询。太棒了!

然而,当我还想返回成员数组(每个成员都嵌套了用户信息)时,它会执行一个额外的查询来获取成员,然后对每个成员执行一个额外的查询来获取用户对象。

我在Django REST框架中特别注意到这一点,我有这样的序列化器:

class MembershipSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Membership
fields = ["is_dm", "user"]

class CampaignSerializer(serializers.ModelSerializer):
owner = UserSerializer()
members = MembershipSerializer(many=True, read_only=True, source='membership_set')
class Meta:
model = Campaign
fields = '__all__'

如果你的活动有6个成员,那么总共会有8个查询,这对我来说似乎很愚蠢。

我希望prefetch_related能解决这个问题:

>>> campaigns = user.campaigns.select_related('owner').prefetch_related()
>>> campaigns[0].members.all()[0].name

但这仍然执行3个查询:一个用于campaign(内部连接用户表的所有者),一个用于成员资格,一个用于第一个用户。

prefetch_related在这里肯定有帮助,但这取决于您如何使用这些关系。

如果您想使用Campaignmembers字段,则:

>>> campaigns = user.campaigns.select_related('owner').prefetch_related('members')
>>> campaigns[0].members.all()[0].name

这将导致两个查询:

  1. 获取campaign的左外连接以获取owner
  2. 获取所有相关成员(已经User实例)。

如果您想使用CampaignMembership的关系-即membership_set,就像您在序列化器中使用它一样:

>>> campaigns = user.campaigns.select_related('owner').prefetch_related('membership_set__user')
>>> campaigns[0].membership_set.all()[0].user.name

这将导致三个查询:

  1. 获取campaign的左外连接以获取owner
  2. 获取所有相关Membership实例
  3. Membershipuser外键中取出所有相关的Users(基于#2的结果)

编辑:

为了进一步减少第二种方法的查询,您可以使用Prefetch指定一个自定义查询集,其中您可以使用select_relatedMembership中获取User:

>>> campaigns = user.campaigns.select_related('owner').prefetch_related(Prefetch('membership_set', queryset=Membership.objects.all().select_related('user')))
>>> campaigns[0].membership_set.all()[0].user.name

这将对Membership和相关的Users进行连接,然后最终只会产生2个查询。

相关内容

  • 没有找到相关文章

最新更新