多対多のN+1問題
解決済
回答 6
2022/03/14 11:35
質問内容

実現したいこと

  • N+1問題を解決したい

発生している問題

いつも動画・ブログでお世話になっております。 <br /> DRFで多対多の参照先がさらにForeignKeyのフィールドを読み込む場合<br /> (下記の例だとBlog ←→ sponsor → user, price)にviewsでprefetchを指定してもN+1回読み込まれてしまいます。 <br />

ソースコード

※該当する箇所だけ簡略化して書いています。

# models.py
class User(TimeStampedModel):
    name = models.CharField()

class Price(TimeStampedModel):
    price = models.PositiveIntegerField()

## 記事を応援してくれた人という設定
class Sponsor(TimeStampedModel):
    user = models.ForeignKey(User)
    price = models.ForeignKey(Price)

class Blog(TimeStampedModel):
    title = models.CharField()
    ...
    sponsor = models.ManyToManyField(Sponsor, blank=True)

## 記事のいいね機能
class Likes(TimeStampedModel):
    blog = models.ForeignKey(Blog)
    user = models.ForeignKey(User)

# serializer.py
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

class PriceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Price
        fields = '__all__'

class SponsorSerializer(serializers.ModelSerializer):
    user = UserSerializer()
    price = PriceSerializer()
    class Meta:
        model = Sponsor
        fields = '__all__'

class BlogSerializer(serializers.ModelSerializer):
    sponsor = SponsorSerializer(many=True)
    likes = serializers.SerializerMethodField()

    def get_likes(self, obj):
        likes = LikeData.objects.filter(blog=obj).count()
        user = self.context['request'].user
        is_liked = LikeData.objects.filter(user=user, blog=obj).exists()

        return {'likes': likes, 'is_liked': is_liked}    

    class Meta:
        model = Blog
        fields = '__all__'


# views.py
class BlogViewSet(viewsets.ModelViewSet):
    queryset = Blog.objects.all()
    serializer_classes = BlogSerializer

自分で試したこと

1 . querysetに対してPrefetch

class BlogViewSet(viewsets.ModelViewSet):
    queryset = Blog.objects.all().prefetch_related(Prefetch('sponsor', Sponsor.objects.select_related('user', 'price')))

としましたが、UserとPriceが何回も重複して呼ばれてしまいました。

2 . django-auto-prefetchingの導入 SerializerMethodFieldがあると自動で認識してくれず、少し複雑になり、エラーが起こったため諦めました

質問

実際のものとは少し変えて書いてあるので、テーブル構造を変えて対応するのではなく、クエリ側での改善を試みたいです。 Prefetchの理解に誤りがあるのでしょうか?
それともこのケースだと生SQLで対応しないと無理なのでしょうか。 知見をいただきたいです!よろしくお願いいたします。

補足情報

バージョン

  • Django==4.0.1
  • djangorestframework==3.13.1
  • Python 3.9.10
回答 6
ベストアンサーを選択すると、解決済みとなります。
nodata
まだ回答がありません
回答
nodata
回答するにはログインが必要です