概括
- 问题n+1查询
- 了解Django中的问题情况
- criaabo£o da api com drf
- 使用padrã£o 的djangodispísta进行解决
- 通过获得或Django虚拟模型
- 参考
介绍什么问题n+1查询
问题n+1查询是应用程序中实体之间存在某些关联的问题,因此,当有必要从A中访问信息时,请为每个实体提供许多要求,以使每个实体都有很多要求应用程序在生产中时,应用程序缓慢,可能会花更多的钱。
了解Django中的问题
为了理解n+1查询的问题,让我们去示例,想象一个平台使用帖子视频的应用程序,并且这些视频具有某些类别,用户路径可以添加,以便视频进入喜欢这些类别的人。通过这种方式,我们可以拥有以下图表实体关系:
我们可以考虑第一个关系,用户发布许多视频,而视频是用户,我们对许多人有关系1。想象一下,我们在整个平台上有3个常规(现在开始),每个平台都发布了1个视频(一个帖子母亲),并且我们的API路线中有一条路线,向我们展示了从常规和常规和视频的标题中显示的信息已发布,需要1个查询,以捕获所有用户和N Dakes,以获取与每个用户相关的视频中的信息。所以我们有:
SELECT * FROM User ...
SELECT title FROM Video WHERE userID = 1
SELECT title FROM Video WHERE userID = 2
SELECT title FROM Video WHERE userID = 3
当我们增加常规数量时,就会发生大问题。关于第二个关联,想法是相同的。因此,我们已经可以想象解决问题的方法,我需要进行查询以获取所有常规和另一个问题,以获取所有常规的vides
SELECT * FROM User ...
SELECT title FROM Video WHERE userID in (1,2,3)
但是出现了问题,您如何使用Django Orm?
使用DRF创建API
pron© - 条件
- 对Django操作的基本理解
- 对Django REST框架操作的基本理解
mass
创建一个名为 video_platform 的文件夹,并安装虚拟环境以不污染您的环境。
python -m venv venv
安装依赖:
pip install django djangorestframework
创建项目和应用程序:
django-admin startproject video_plataform .
django-admin startapp core
设置
随后,让我们添加到settings.py文件两个应用程序
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"core.apps.CoreConfig", #<- new
"rest_framework" #<- new
]
楷模
为了制作映射,我们可以在model.py文件中添加以下代码并迁移到数据库:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=30)
def __str__(self) -> str:
return self.name
class Video(models.Model):
title = models.CharField(max_length=30)
url = models.CharField(max_length=255)
visualizations = models.IntegerField(default=0)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="videos")
def __str__(self) -> str:
return self.title
class Category(models.Model):
title = models.CharField(max_length=30)
videos = models.ManyToManyField(Video, related_name="categories")
def __str__(self) -> str:
return self.title
python manage.py makemigrations
python manage.py migrate
填充数据库
创建一个名为“固定装置”的董事
[ {
"model": "core.user",
"pk": 1,
"fields": {
"name": "Paul"
}
},{
"model": "core.video",
"pk": 1,
"fields": {
"title": "Dancinha do loirinho",
"url":"www.google.com.br",
"visualizations": 0,
"user": 1
}
},{
"model": "core.user",
"pk": 2,
"fields": {
"name": "Marie"
}
},{
"model": "core.video",
"pk": 2,
"fields": {
"title": "Dancinha do forró",
"url":"www.google.com.br",
"visualizations": 0,
"user": 2
}
},{
"model": "core.user",
"pk": 3,
"fields": {
"name": "Mary"
}
},{
"model": "core.video",
"pk": 3,
"fields": {
"title": "The office",
"url":"www.google.com.br",
"visualizations": 0,
"user": 3
}
},{
"model": "core.category",
"pk": 1,
"fields": {
"title": "Entretenimento",
"videos":[1,2,3]
}
},{
"model": "core.category",
"pk": 2,
"fields": {
"title": "Forró",
"videos":[2]
}
},{
"model": "core.category",
"pk": 3,
"fields": {
"title": "Funk",
"videos":[1]
}}]
在终端运行命令:
python manage.py loaddata data.json
序列化器
创建 serializers.py 文件并放置遏制:
from rest_framework import serializers
from core.models import User, Video, Category
class VideoSerializer(serializers.ModelSerializer):
class Meta:
model = Video
fields = ("title","url","visualizations")
class UserSerializer(serializers.ModelSerializer):
videos = VideoSerializer(many=True)
class Meta:
model = User
fields = ("name", "videos")
class CategorySerializer(serializers.ModelSerializer):
videos = VideoSerializer(many=True)
class Meta:
model = Category
fields = ("title")
URL
在项目文件夹中的urls.py文件中添加以下命令:
from django.contrib import admin
from django.urls import path
from core.views import UserListAPIView #<- new
urlpatterns = [
path('admin/', admin.site.urls),
path('users/', UserListAPIView.as_view()) #<- new
]
看法
为了能够列出我们的应用程序的用户用途,我们将创建视图 useristapiview
from rest_framework.generics import ListAPIView
from core.serializers import UserSerializer
from core.models import User
from django.db import connection, reset_queries
class UserListAPIView(ListAPIView):
serializer_class = UserSerializer
def get_queryset(self):
users = User.objects.all()
for query in connection.queries:
print("Query:", query)
reset_queries()
return users
的方式有些好奇,我们有一个从listApiview继承的类,因为我希望它以序列化的方式执行特定资源的所有冲动的清单功能,因此我做o user.objects.all.abignts.all.abect.all.a )命令,但是Django使我们能够使用连接属性知道对银行制作了什么Dakes。BR>
Query: {'sql': 'SELECT "core_user"."id", "core_user"."name" FROM "core_user"', 'time': '0.004'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" = 1', 'time': '0.003'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" = 2', 'time': '0.000'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" = 3', 'time': '0.000'}
如果您记得与我之前提出的相同的疑问,也就是说,我们可以在这里看到问题,如果通常的数量增加,最大的是为此观点捕捉视频的宠儿数量。
使用Django具有标准的解决方案解决
要解决此问题,我们将修改使用ORM的方式,因此请修改:
#users = User.objects.all()
#=>
users = User.objects.prefetch_related("videos")
引起我们正在进行反向访问,因为那些具有外键的人是视频和用户模型,我们必须使用preapeab_realated()进行2个查询。而且它可以通过python,通过python,我们还拥有执行相同功能的select_recyated(),但应在具有外键并通过数据库进行连接的模型中使用,因此仅执行1个查询。因此,如果我们访问路线并前往终端,我们将看到类似的东西:
Query: {'sql': 'SELECT "core_user"."id", "core_user"."name" FROM "core_user"', 'time': '0.004'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" IN (1, 2, 3)', 'time': '0.003'}
正如我们在查询中可以看到的,即使系统有100万个常规,只有2个申请数据库,而不是100万请求1加1。
。即将
让我们在显示的用户数据中添加一个新数据,我们可以显示每个用户拥有的 videos 的数量,我们可以添加以下IDE在 serializ.py文件中:
class UserSerializer(serializers.ModelSerializer):
videos = VideoSerializer(many=True)
amount_videos = serializers.SerializerMethodField() #<-new
class Meta:
model = User
fields = ("name", "videos", "amount_videos") #<-new
def get_amount_videos(self, obj): #<-new
return Video.objects.filter(user=obj).count() #<-new
以上将创建数据中的新密钥,并以每种常规的视频数量发送给客户,我们可以看到终端所做的daries:
Query: {'sql': 'SELECT "core_user"."id", "core_user"."name" FROM "core_user"', 'time': '0.002'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" IN (1, 2, 3)', 'time': '0.001'}
Query: {'sql': 'SELECT COUNT(*) AS "__count" FROM "core_video" WHERE "core_video"."user_id" = 1', 'time': '0.001'}
Query: {'sql': 'SELECT COUNT(*) AS "__count" FROM "core_video" WHERE "core_video"."user_id" = 2', 'time': '0.000'}
Query: {'sql': 'SELECT COUNT(*) AS "__count" FROM "core_video" WHERE "core_video"."user_id" = 3', 'time': '0.000'}
看,我们的两个查询仍然存在(一切都很好!),我们避免了n+1查询的问题,但是要知道每个常规的vade量仍然存在。>>>>>>>。
了解Django虚拟模型
为了解决提出的问题,其中一种形式是使用一个名为Django虚拟模型的库,此库为我们提供了一种解决n+1查询问题的方法,但仍然可以用高收获性。
安装库:
pip install django-virtual-model
在应用程序中创建一个名为virtual_models.py的文件,如下:
import django_virtual_models as v
from core.models import User, Video
from django.db.models import Count
class VirtualVideo(v.VirtualModel):
class Meta:
model = Video
class VirtualUser(v.VirtualModel):
videos = VirtualVideo(manager=Video.objects)
amount_videos = v.Annotation(
lambda qs, **kwargs: qs.annotate(
amount_videos=Count("videos")
).distinct()
)
class Meta:
model = User
以上将创建虚拟模型,这些模型在从VADE及其数量开始的过程中为我们提供了帮助,我们可以看到我们正在使用注释来添加金额_VIDEOS属性属性的VIDE的数量。 p>
在我们的serializer.py文件中,我们可以将愉快的序列化器修改为以下内容:
import django_virtual_models as dvm
class UserSerializer(dvm.VirtualModelSerializer):
videos = VideoSerializer(many=True)
amount_videos = serializers.IntegerField(read_only=True)
class Meta:
model = User
virtual_model = VirtualUser
fields = ("name", "videos","amount_videos")
,我们的观点将如下:
class UserListAPIView(dvm.VirtualModelListAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_queryset(self):
queryset = super().get_queryset()
for query in connection.queries:
print("Query:", query)
reset_queries()
return queryset
在上面,我们已经在使用一般QuerySet,因为我们的库已经谨慎地解决了我们的Vade问题以及视频的数量。因此,当我们访问路线并查看终端时,我们可以看到类似的东西:
Query: {'sql': 'SELECT DISTINCT "core_user"."id", "core_user"."name", COUNT("core_video"."id") AS "amount_videos" FROM "core_user" LEFT OUTER JOIN "core_video" ON ("core_user"."id" = "core_video"."user_id") GROUP BY "core_user"."id", "core_user"."name"', 'time': '0.001'}
Query: {'sql': 'SELECT "core_video"."id", "core_video"."title", "core_video"."url", "core_video"."visualizations", "core_video"."user_id" FROM "core_video" WHERE "core_video"."user_id" IN (1, 2, 3)', 'time': '0.000'}
我们可以看到我们回到了2个查询的数量,因为在获取数据的查询中已经计数视频。冷却!?
已开发的代码:Repositório