Django - 太多类似的查询
Django - Too many similar queries
我正在创建一个音乐评级应用程序,我正在为有很多关系的专辑制作一个序列化程序,我认为这是造成所有麻烦的一种聚合方法序列化程序。该方法获取每张专辑的平均评论数和评论数。 有什么方法可以减少查询数量以提高性能?
我所有的模型
class Artist(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
image = models.FileField(null=True, blank=True,
upload_to=rename_artist_image)
background_image = models.FileField(
null=True, blank=True, upload_to=rename_artist_bg_image)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.name
class Album(models.Model):
RELEASE_TYPE_ALBUM_CHOICES = [
("LP", "LP"),
("EP", "EP"),
("Single", "Single"),
("Live", "Live"),
]
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
release_date = models.DateField(null=True)
artist_id = models.ForeignKey(
Artist, on_delete=models.PROTECT, related_name="albums"
)
art_cover = models.FileField(
null=True, blank=True, upload_to=rename_album_art_cover)
release_type = models.CharField(max_length=10,
choices=RELEASE_TYPE_ALBUM_CHOICES, default="LP")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.title
class Genre(models.Model):
name = models.CharField(max_length=255)
def __str__(self) -> str:
return self.name
class AlbumGenre(models.Model):
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_genres")
genre_id = models.ForeignKey(
Genre, on_delete=models.PROTECT, related_name="album_genres")
def __str__(self) -> str:
return self.genre_id.name
class AlbumLink(models.Model):
SERVICE_NAME_CHOICES = [
("spotify", "Spotify"),
("tidal", "Tidal"),
("amazonMusic", "Amazon Music"),
("appleMusic", "Apple Music"),
]
service_name = models.CharField(
max_length=15, choices=SERVICE_NAME_CHOICES)
url = models.CharField(max_length=255)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_links")
def __str__(self) -> str:
return f"{self.service_name} - {self.url}"
class Track(models.Model):
title = models.CharField(max_length=255)
position = models.PositiveIntegerField()
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="tracks")
duration = models.DurationField(null=True)
def __str__(self) -> str:
return f"{self.position}. {self.title} - {self.duration}"
class AlbumOfTheYear(models.Model):
album_id = models.OneToOneField(
Album, on_delete=models.PROTECT, related_name="aoty")
position = models.IntegerField()
def __str__(self) -> str:
return str(self.position)
class Review(models.Model):
reviewer_id = models.ForeignKey(Reviewer, on_delete=models.PROTECT)
rating = models.IntegerField(
validators=[MaxValueValidator(100), MinValueValidator(0)]
)
review_text = models.TextField(null=True)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="reviews")
created_at = models.DateTimeField(auto_now_add=True)
这就是相册序列化器的样子。
"id": 2,
"title": "OK Computer",
"slug": "ok-computer",
"created_at": "2022-02-22T21:51:52.528148Z",
"artist": {
"id": 13,
"name": "Radiohead",
"slug": "radiohead",
"image": "http://127.0.0.1:8000/media/artist/images/radiohead.jpg",
"background_image": "http://127.0.0.1:8000/media/artist/bg_images/radiohead.jpg",
"created_at": "2022-02-22T00:00:00Z"
},
"art_cover": "http://127.0.0.1:8000/media/album/art_covers/ok-computer_cd5Vv6U.jpg",
"genres": [
"Alternative Rock",
"Art Rock"
],
"reviews": {
"overall_score": null,
"number_of_ratings": 0
},
"release_date": "1997-05-28",
"release_type": "LP",
"tracks": [
{
"position": 1,
"title": "Airbag",
"duration": "00:04:47"
},
{
"position": 2,
"title": "Paranoid Android",
"duration": "00:06:27"
}
],
"links": [
{
"service_name": "spotify",
"url": "https://open.spotify.com/album/6dVIqQ8qmQ5GBnJ9shOYGE?si=L_VNH3HeSMmGBqfiqKiGWA"
}
],
"aoty": null
专辑序列化程序,其方法是获取专辑评论的平均值和计数
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
genres = StringRelatedField(
source="album_genres", many=True, read_only=True)
aoty = StringRelatedField(read_only=True)
links = AlbumLinkSerializer(
source="album_links", many=True, read_only=True)
artist = SimpleArtistSerializer(source="artist_id")
def get_avg_and_count_of_reviews(self, album: Album):
reviews = Review.objects.only("rating").filter(album_id=album.id).aggregate(
overall_score=Avg(F("rating"), output_field=IntegerField()), number_of_ratings=Count(F("rating"), output_field=IntegerField()))
return reviews
reviews = serializers.SerializerMethodField(
method_name="get_avg_and_count_of_reviews")
class Meta:
model = Album
fields = ["id",
"title",
"slug",
"created_at",
"artist",
"art_cover",
"genres",
"reviews",
"release_date",
"release_type",
"tracks",
"links",
"aoty"]
# Save slug
def create(self, validated_data):
slug = slugify(validated_data["title"])
return Album.objects.create(slug=slug, **validated_data)
这是相册Viewset中的queryset
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related("tracks").prefetch_related("album_genres").prefetch_related(
"album_links").prefetch_related("reviews").select_related("aoty").select_related("artist_id").all()
首先,您需要将为每个专辑调用一次的聚合更改为注释,这将删除所有这些额外的聚合查询
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related(
"tracks",
"album_genres",
"album_links",
"reviews"
).select_related(
"aoty",
"artist_id"
).annotate(
overall_score=Avg(F("reviews__rating"), output_field=IntegerField()),
number_of_ratings=Count(F("reviews__rating"), output_field=IntegerField())
)
现在您可以用两个常规 IntegerFields
替换 reviews
字段
class AlbumSerializer(serializers.ModelSerializer):
...
overall_score = serializers.IntegerField(source="overall_score")
number_of_ratings = serializers.IntegerField(source="number_of_ratings")
我正在创建一个音乐评级应用程序,我正在为有很多关系的专辑制作一个序列化程序,我认为这是造成所有麻烦的一种聚合方法序列化程序。该方法获取每张专辑的平均评论数和评论数。 有什么方法可以减少查询数量以提高性能?
我所有的模型
class Artist(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
image = models.FileField(null=True, blank=True,
upload_to=rename_artist_image)
background_image = models.FileField(
null=True, blank=True, upload_to=rename_artist_bg_image)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.name
class Album(models.Model):
RELEASE_TYPE_ALBUM_CHOICES = [
("LP", "LP"),
("EP", "EP"),
("Single", "Single"),
("Live", "Live"),
]
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
release_date = models.DateField(null=True)
artist_id = models.ForeignKey(
Artist, on_delete=models.PROTECT, related_name="albums"
)
art_cover = models.FileField(
null=True, blank=True, upload_to=rename_album_art_cover)
release_type = models.CharField(max_length=10,
choices=RELEASE_TYPE_ALBUM_CHOICES, default="LP")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.title
class Genre(models.Model):
name = models.CharField(max_length=255)
def __str__(self) -> str:
return self.name
class AlbumGenre(models.Model):
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_genres")
genre_id = models.ForeignKey(
Genre, on_delete=models.PROTECT, related_name="album_genres")
def __str__(self) -> str:
return self.genre_id.name
class AlbumLink(models.Model):
SERVICE_NAME_CHOICES = [
("spotify", "Spotify"),
("tidal", "Tidal"),
("amazonMusic", "Amazon Music"),
("appleMusic", "Apple Music"),
]
service_name = models.CharField(
max_length=15, choices=SERVICE_NAME_CHOICES)
url = models.CharField(max_length=255)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="album_links")
def __str__(self) -> str:
return f"{self.service_name} - {self.url}"
class Track(models.Model):
title = models.CharField(max_length=255)
position = models.PositiveIntegerField()
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="tracks")
duration = models.DurationField(null=True)
def __str__(self) -> str:
return f"{self.position}. {self.title} - {self.duration}"
class AlbumOfTheYear(models.Model):
album_id = models.OneToOneField(
Album, on_delete=models.PROTECT, related_name="aoty")
position = models.IntegerField()
def __str__(self) -> str:
return str(self.position)
class Review(models.Model):
reviewer_id = models.ForeignKey(Reviewer, on_delete=models.PROTECT)
rating = models.IntegerField(
validators=[MaxValueValidator(100), MinValueValidator(0)]
)
review_text = models.TextField(null=True)
album_id = models.ForeignKey(
Album, on_delete=models.PROTECT, related_name="reviews")
created_at = models.DateTimeField(auto_now_add=True)
这就是相册序列化器的样子。
"id": 2,
"title": "OK Computer",
"slug": "ok-computer",
"created_at": "2022-02-22T21:51:52.528148Z",
"artist": {
"id": 13,
"name": "Radiohead",
"slug": "radiohead",
"image": "http://127.0.0.1:8000/media/artist/images/radiohead.jpg",
"background_image": "http://127.0.0.1:8000/media/artist/bg_images/radiohead.jpg",
"created_at": "2022-02-22T00:00:00Z"
},
"art_cover": "http://127.0.0.1:8000/media/album/art_covers/ok-computer_cd5Vv6U.jpg",
"genres": [
"Alternative Rock",
"Art Rock"
],
"reviews": {
"overall_score": null,
"number_of_ratings": 0
},
"release_date": "1997-05-28",
"release_type": "LP",
"tracks": [
{
"position": 1,
"title": "Airbag",
"duration": "00:04:47"
},
{
"position": 2,
"title": "Paranoid Android",
"duration": "00:06:27"
}
],
"links": [
{
"service_name": "spotify",
"url": "https://open.spotify.com/album/6dVIqQ8qmQ5GBnJ9shOYGE?si=L_VNH3HeSMmGBqfiqKiGWA"
}
],
"aoty": null
专辑序列化程序,其方法是获取专辑评论的平均值和计数
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
genres = StringRelatedField(
source="album_genres", many=True, read_only=True)
aoty = StringRelatedField(read_only=True)
links = AlbumLinkSerializer(
source="album_links", many=True, read_only=True)
artist = SimpleArtistSerializer(source="artist_id")
def get_avg_and_count_of_reviews(self, album: Album):
reviews = Review.objects.only("rating").filter(album_id=album.id).aggregate(
overall_score=Avg(F("rating"), output_field=IntegerField()), number_of_ratings=Count(F("rating"), output_field=IntegerField()))
return reviews
reviews = serializers.SerializerMethodField(
method_name="get_avg_and_count_of_reviews")
class Meta:
model = Album
fields = ["id",
"title",
"slug",
"created_at",
"artist",
"art_cover",
"genres",
"reviews",
"release_date",
"release_type",
"tracks",
"links",
"aoty"]
# Save slug
def create(self, validated_data):
slug = slugify(validated_data["title"])
return Album.objects.create(slug=slug, **validated_data)
这是相册Viewset中的queryset
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related("tracks").prefetch_related("album_genres").prefetch_related(
"album_links").prefetch_related("reviews").select_related("aoty").select_related("artist_id").all()
首先,您需要将为每个专辑调用一次的聚合更改为注释,这将删除所有这些额外的聚合查询
class AlbumViewSet(ModelViewSet):
queryset = Album.objects.prefetch_related(
"tracks",
"album_genres",
"album_links",
"reviews"
).select_related(
"aoty",
"artist_id"
).annotate(
overall_score=Avg(F("reviews__rating"), output_field=IntegerField()),
number_of_ratings=Count(F("reviews__rating"), output_field=IntegerField())
)
现在您可以用两个常规 IntegerFields
替换reviews
字段
class AlbumSerializer(serializers.ModelSerializer):
...
overall_score = serializers.IntegerField(source="overall_score")
number_of_ratings = serializers.IntegerField(source="number_of_ratings")